R/visPed.R

Defines functions visEngine visPed

Documented in visEngine visPed

#' Visualize a pedigree with cancer affection statuses and other information
#'
#' This function relies on the kinship2 package
#'
#' @param ped a pedigree that contains \code{ID, Sex, MotherID, FatherID,
#' isProband, CurAge}
#' It is assumed that in this data.frame, Sex = 1 if male, Sex = 0 if female
#' and Sex = NA if unkonwn
#' Affliction is coded in "isAffXX" columns, where XX are short names of cancers
#' The age of diagnosis is correspondingly coded in "AgeXX" columns
#' @param annot.cancers the cancers shortnames to display. When set to
#' default /code{NULL}, the top four cancers will be displayed.
#' @param annot.feature ONE feature that we would get annotation from pedigree,
#'  one of the choices from \code{c("Ancestry","Twins","CurAge","race")}
#' @param title a string for the title on the plot
#' @importFrom grDevices colorRampPalette
#' @importFrom graphics arrows frame legend lines par points polygon segments strheight strwidth text title
#' @examples
#' visPed(test_fam_1)
#' @export
visPed <- function(ped, annot.cancers = "all", annot.feature = "CurAge",
                   title = "Your Pedigree") {
  # Translate columns of pedigree into kinship2 standard
  # In kinship2, Sex = 1 when male, Sex = 2 when female and Sex = 3 when unknown
  # We assume that ped has -999 for missing MotherID or FatherID
  ks_df <- with(ped, {
    Sex <- ifelse(Sex == 0, 2, Sex)
    Sex <- ifelse(is.na(Sex), 3, Sex)

    MotherID <- ifelse(MotherID == -999, NA, MotherID)
    FatherID <- ifelse(FatherID == -999, NA, FatherID)

    cbind(ID, FatherID, MotherID, Sex, isDead)
  })

  # Translate column names
  colnames(ks_df) <- c("id", "dadid", "momid", "sex", "censor")

  # Create family object(s) in kinship2
  fam_vec <- kinship2::makefamid(
    id = ks_df[, "id"],
    father.id = ks_df[, "dadid"],
    mother.id = ks_df[, "momid"]
  )

  # Exit if there are disconnected families
  if (length(unique(fam_vec)) > 1) {
    rlang::abort(paste0("Pedigree contains disconnected families, ",
                 "please first double check with kinship2::makefamid"),
      level = "DisconnectedFamily"
    )
  }

  # Currently supported cancers: BC OC BRA COL ENDO GAS KID MELA PANC PROS SI (SMA renamed)
  supported_cancers <- c("BRA", "BC", "CER", "COL", "ENDO",
                         "GAS", "KID", "LEUK", "MELA", "OC",
                         "OST", "PANC", "PROS", "SI",
                         "STS", "THY", "UB", "HEP")

  # Get the cancers to plot
  cancers_in_pedigree <- substring(colnames(ped[, grepl("isAff", names(ped)),
                                                drop = FALSE]), 6)

  if (annot.cancers == "all") {
    cancer_status <- ped[, paste0("isAff", cancers_in_pedigree), drop = FALSE]
    non_appearing_cancers <- paste0("isAff", setdiff(supported_cancers,
                                                     cancers_in_pedigree))
    if (length(non_appearing_cancers) == 1 && non_appearing_cancers == "isAff") {
      # if all cancers present, don't need to do anything
    } else {
      # when there are some missing cancers, set them all to 0
      cancer_status[non_appearing_cancers] <- 0
    }
    cancers_to_plot <- cancers_in_pedigree
  }

  # Reorder cancer columns so that they are always consistent across plots
  cancer_status <- cancer_status[, order(colnames(cancer_status))]

  # Set up the pedigree object in kinship2
  ks_ped <- kinship2::pedigree(
    id = ks_df[, "id"],
    dadid = ks_df[, "dadid"],
    momid = ks_df[, "momid"],
    sex = ks_df[, "sex"],
    affected = as.matrix(cancer_status),
    status = ks_df[, "censor"]
  )


  # Set up annotations
  annotations <- NULL
  if (!is.null(annot.cancers)) {
    cancer_ages <- ped[, paste0("Age", cancers_to_plot), drop = FALSE]

    age_list <- list()
    for (cancer in cancers_to_plot) {
      # Get the affection status age - it could be NA
      # Avoid writing anything if there is an NA
      affection_of_cancer <- ped[[paste0("isAff", cancer)]]
      age_of_cancer <- ped[[paste0("Age", cancer)]]
      # Remove incorrectly placed ages - make sure that person is affected
      ages_to_annotate <- ifelse(affection_of_cancer,
        age_of_cancer,
        NA
      )
      # If they are affected, but missing age, put "NA" as the age
      ages_to_annotate[is.na(ages_to_annotate) & affection_of_cancer] <- "NA"
      ages_to_annotate[is.na(ages_to_annotate)] <- ""
      annotation_string <- ifelse(ages_to_annotate == "",
        "",
        paste(cancer, ages_to_annotate, sep = ": ")
      )
      age_list[[cancer]] <- annotation_string
    }

    annotations <- do.call(paste, c(age_list, list(sep = "\n")))

    # Define a recursive function which cuts of excess \ns from the end
    cut_n <- function(string) {
      if (regmatches(string, regexpr(".{2}$", string)) == "\n\n") {
        output <- substr(string, 1, nchar(string) - 1)
        return(cut_n(output))
      } else {
        return(string)
      }
    }

    # Loop over the annotations to fix them
    for (i in 1:length(annotations)) {
      if (annotations[i] == "\n" ||
          all(unlist(strsplit(annotations[i], split = "\n")) == "")) {
        # If the entire string is \n or \n repeated, get rid of it
        annotations[i] <- ""
      } else if (unlist(strsplit(annotations[i], split = "\n"))[1] == "") {
        # If there is a \n at the start, get rid of it
        annotations[i] <- substring(annotations[i], 2)
      }
      if (annotations[i] != "" && annotations[i] != "\n") {
        # Put a \n at the end if there is actually a notation
        # But don't if there is already one
        annotations[i] <- cut_n(paste0(annotations[i], "\n"))
      }

      # There might still be "\n" at the start... remove them
      # We might need to do this recursively...
      while (!identical(strsplit(annotations[i], split = "\n")[[1]], character(0)) &
        substr(annotations[i], 1, 1) == "\n") {
        annotations[i] <- substring(annotations[i], 2)
      }

      # If there are multiple \n between cancers, make sure there is only one
      double_ns <- strsplit(annotations[i], split = "\n")[[1]] == ""
      if (!identical(which(double_ns), integer(0))) {
        # Get rid of the indexes
        annotations[i] <- paste(paste(strsplit(annotations[i], split = "\n")[[1]][!double_ns],
          collapse = "\n"
        ),
        "\n",
        collapse = ""
        )
      }
    }
  }

  if (!is.null(annot.feature)) {
    annot.feature <- intersect(
      c("Twins", "Ancestry", "CurAge", "race", "ID"),
      annot.feature
    )
    features_cols <- ped[c(annot.feature, "isDead")]
    feature_annotations <- lapply(1:nrow(ped), function(i) {
      mark <- substr(features_cols[i, 1], 1, 3)
      if (annot.feature == "CurAge") {
        annot.feature <- "Age"
      }
      annot.feature <- substr(annot.feature, 1, 3)
      # If the mark is na, write that
      mark[is.na(mark)] <- "NA"
      # mark <- mark[!is.na(mark)]
      # Do not plot Age if isDead==1
      # if (!(annot.feature == "Age" && features_cols[i, "isDead"] == 1)) {
      #   paste(annot.feature, mark, sep = ": ")
      # }
      paste(annot.feature, mark, sep = ": ")
    })
    annotations <- paste0(annotations, sapply(feature_annotations,
      paste0,
      collapse = "\n"
    ))
  }

  # Identify proband
  probands <- which(ped$isProband == 1)

  # Plot using kinship2 engine
  visEngine(ks_ped,
    annot = annotations,
    feature.name = annot.feature,
    which.proband = probands,
    main_title = title
  )

  invisible(ks_ped)
}

#' Visualization engine inspired by kinship2
#'
#' @param x a pedigree data.frame
#' @param annot the annotation string
#' @param feature.name the names of the features
#' @param which.proband which ID is the proband
#' @param add.ids which additional IDs to add
#' @param id ID column from x pedigree
#' @param status alive or dead status
#' @param affected affected or unaffected column
#' @param cex spacing
#' @param col column number, either 1 or the number of individuals
#' @param symbolsize symbol size
#' @param branch branch size
#' @param packed boolean to pack the pedigree plot
#' @param align align parameters
#' @param width width parameter
#' @param density density parameters
#' @param main_title title on plot
#' @param mar legend margins
#' @param angle angle of labels
#' @param keep.par boolean to keep or discard old graphics parameters
#' @param subregion boolean whether or not to split plot into subregions
#' @param pconnect controls parent connections
#' @import kinship2
visEngine <- function(x, annot, feature.name = NULL,
                      which.proband = NULL, add.ids = x$id[x$add == 1],
                      id = x$id, status = x$status, affected = x$affected,
                      cex = 1, col = 1, symbolsize = 1, branch = 0.6, packed = TRUE,
                      align = c(1.5, 2), width = 10, density = c(-1, 35, 65, 20),
                      main_title = "Your Pedigree",
                      mar = c(4.1, 1, 4.1, 1), angle = c(90, 65, 40, 0), keep.par = FALSE,
                      subregion, pconnect = 0.5) {
  getPalette <- colorRampPalette(RColorBrewer::brewer.pal(9, "Set1"))

  Call <- match.call()
  affected[is.na(affected)] <- -1

  colmat <- getPalette(ncol(affected))
  density <- rep(-1, ncol(affected))
  angle <- rep(90, ncol(affected))

  n <- length(x$id)
  if (is.null(status)) {
    status <- rep(0, n)
  } else {
    if (!all(status == 0 | status == 1)) {
      stop("Invalid status code")
    }
    if (length(status) != n) {
      stop("Wrong length for status")
    }
  }
  if (!missing(id)) {
    if (length(id) != n) {
      stop("Wrong length for id")
    }
  }
  if (is.null(affected)) {
    affected <- matrix(0, nrow = n)
  }
  else {
    if (is.matrix(affected)) {
      if (nrow(affected) != n) {
        stop("Wrong number of rows in affected")
      }
      if (is.logical(affected)) {
        affected <- 1 * affected
      }
      # the maximum number of cancers a individual can have
      affect_indicator <- affected != 0
      maxncancer <- max(apply(affect_indicator, 1, sum))
      # if (maxncancer > length(angle) || maxncancer > length(density))
      #     stop("More columns in the affected matrix than angle/density values")
    }
    else {
      if (length(affected) != n) {
        stop("Wrong length for affected")
      }
      if (is.logical(affected)) {
        affected <- as.numeric(affected)
      }
      if (is.factor(affected)) {
        affected <- as.numeric(affected) - 1
      }
    }
    # if (max(affected, na.rm = TRUE) > min(affected, na.rm = TRUE)) {
    #     affected <- matrix(affected - min(affected, na.rm = TRUE),
    #                        nrow = n)
    # }
    # else {
    #     affected <- matrix(affected, nrow = n)
    # }
    # if (!all(affected == 0 | affected == 1 | affected == -1))
    #     print(affected)
    #     print("adfasd")
    #     stop("Invalid code for affected status")
  }
  if (length(col) == 1) {
    col <- rep(col, n)
  } else if (length(col) != n) {
    stop("Col argument must be of length 1 or n")
  }

  # sub-region -------

  # plist

  # subreg

  subregion2 <- function(plist, subreg) {
    if (subreg[3] < 1 || subreg[4] > length(plist$n)) {
      stop("Invalid depth indices in subreg")
    }
    lkeep <- subreg[3]:subreg[4]
    for (i in lkeep) {
      if (!any(plist$pos[i, ] >= subreg[1] & plist$pos[i, ] <= subreg[2])) {
        stop(paste("No subjects retained on level", i))
      }
    }
    nid2 <- plist$nid[lkeep, ]
    n2 <- plist$n[lkeep]
    pos2 <- plist$pos[lkeep, ]
    spouse2 <- plist$spouse[lkeep, ]
    fam2 <- plist$fam[lkeep, ]
    if (!is.null(plist$twins)) {
      twin2 <- plist$twins[lkeep, ]
    }
    for (i in 1:nrow(nid2)) {
      keep <- which(pos2[i, ] >= subreg[1] & pos2[i, ] <=
        subreg[2])
      nkeep <- length(keep)
      n2[i] <- nkeep
      nid2[i, 1:nkeep] <- nid2[i, keep]
      pos2[i, 1:nkeep] <- pos2[i, keep]
      spouse2[i, 1:nkeep] <- spouse2[i, keep]
      fam2[i, 1:nkeep] <- fam2[i, keep]
      if (!is.null(plist$twins)) {
        twin2[i, 1:nkeep] <- twin2[i, keep]
      }
      if (i < nrow(nid2)) {
        tfam <- match(fam2[i + 1, ], keep, nomatch = 0)
        fam2[i + 1, ] <- tfam
        if (any(spouse2[i, tfam] == 0)) {
          stop("A subregion cannot separate parents")
        }
      }
    }
    n <- max(n2)
    out <- list(
      n = n2[1:n], nid = nid2[, 1:n, drop = F],
      pos = pos2[, 1:n, drop = F], spouse = spouse2[, 1:n, drop = F], fam = fam2[, 1:n, drop = F]
    )
    if (!is.null(plist$twins)) {
      out$twins <- twin2[, 1:n, drop = F]
    }
    out
  }

  plist <- kinship2::align.pedigree(x, packed = packed, width = width, align = align)

  op <- options(repr.plot.height = nrow(plist$pos) * 10)
  # on.exit(options(op))
  if (!missing(subregion)) {
    plist <- subregion2(plist, subregion)
  }
  xrange <- range(plist$pos[plist$nid > 0])
  maxlev <- nrow(plist$pos)

  sinf <- dim(plist$nid)

  ### size ###

  # dev.new(width = 0.2*sinf[2], height = 20*sinf[1])
  # if (dev.cur() <= 3) dev.off(dev.cur())
  # dev.new(height=30, units = "units")
  frame()

  # allow additional space for cancer legend
  oldpar <- par(xpd = T, mar = mar + c(0, 0, 4, 0))
  # oldpar <- par(mar = mar, xpd = TRUE)
  psize <- par("pin")
  stemp1 <- strwidth("ABC", units = "inches", cex = cex) * 2.5 / 3
  stemp2 <- strheight("1g", units = "inches", cex = cex)
  stemp3 <- max(strheight(id, units = "inches", cex = cex))
  ht1 <- psize[2] / maxlev - (stemp3 + 1.5 * stemp2)
  if (ht1 <= 0) {
    stop("Labels leave no room for the graph, reduce cex")
  }
  ht2 <- psize[2] / (maxlev + (maxlev - 1) / 2)
  wd2 <- 0.8 * psize[1] / (0.8 + diff(xrange))
  boxsize <- symbolsize * min(ht1, ht2, stemp1, wd2)
  hscale <- (psize[1] - boxsize) / diff(xrange)
  vscale <- (psize[2] - (stemp3 + stemp2 / 2 + boxsize)) / max(1, maxlev - 1)
  boxw <- boxsize / hscale
  boxh <- boxsize / vscale
  labh <- stemp2 / vscale
  legh <- min(1 / 4, boxh * 1.5)
  par(usr = c(
    xrange[1] - boxw / 2, xrange[2] + boxw / 2,
    maxlev + boxh + stemp3 + stemp2 / 2, 1
  ))
  circfun <- function(nslice, n = 50) {
    nseg <- ceiling(n / nslice)
    theta <- -pi / 2 - seq(0, 2 * pi, length = nslice + 1)
    out <- vector("list", nslice)
    for (i in 1:nslice) {
      theta2 <- seq(theta[i], theta[i + 1], length = nseg)
      out[[i]] <- list(
        x = c(0, cos(theta2) / 2),
        y = c(0, sin(theta2) / 2) + 0.5
      )
    }
    out
  }
  polyfun <- function(nslice, object) {
    zmat <- matrix(0, ncol = 4, nrow = length(object$x))
    zmat[, 1] <- object$x
    zmat[, 2] <- c(object$x[-1], object$x[1]) - object$x
    zmat[, 3] <- object$y
    zmat[, 4] <- c(object$y[-1], object$y[1]) - object$y
    ns1 <- nslice + 1
    theta <- -pi / 2 - seq(0, 2 * pi, length = ns1)
    x <- y <- double(ns1)
    for (i in 1:ns1) {
      z <- (tan(theta[i]) * zmat[, 1] - zmat[, 3]) / (zmat[, 4] - tan(theta[i]) * zmat[, 2])
      tx <- zmat[, 1] + z * zmat[, 2]
      ty <- zmat[, 3] + z * zmat[, 4]
      inner <- tx * cos(theta[i]) + ty * sin(theta[i])
      indx <- which(is.finite(z) & z >= 0 & z <= 1 & inner > 0)
      x[i] <- tx[indx]
      y[i] <- ty[indx]
    }
    nvertex <- length(object$x)
    temp <- data.frame(
      indx = c(1:ns1, rep(0, nvertex)),
      theta = c(theta, object$theta), x = c(x, object$x),
      y = c(y, object$y)
    )
    temp <- temp[order(-temp$theta), ]
    out <- vector("list", nslice)
    for (i in 1:nslice) {
      rows <- which(temp$indx == i):which(temp$indx == (i + 1))
      out[[i]] <- list(x = c(0, temp$x[rows]), y = c(0, temp$y[rows]) + 0.5)
    }
    out
  }
  if (ncol(affected) == 1) {
    polylist <- list(
      square = list(list(x = c(-1, -1, 1, 1) / 2, y = c(0, 1, 1, 0))),
      circle = list(list(x = 0.5 * cos(seq(0, 2 * pi, length = 50)), y = 0.5 * sin(seq(0, 2 * pi, length = 50)) + 0.5)),
      diamond = list(list(x = c(0, -0.5, 0, 0.5), y = c(0, 0.5, 1, 0.5))),
      triangle = list(list(x = c(0, -0.56, 0.56), y = c(0, 1, 1)))
    )
  }
  else {
    nc <- maxncancer
    square <- polyfun(nc, list(
      x = c(-0.5, -0.5, 0.5, 0.5),
      y = c(-0.5, 0.5, 0.5, -0.5), theta = -c(3, 5, 7, 9) * pi / 4
    ))
    circle <- circfun(nc)
    diamond <- polyfun(nc, list(x = c(0, -0.5, 0, 0.5), y = c(-0.5, 0, 0.5, 0), theta = -(1:4) * pi / 2))
    triangle <- polyfun(nc, list(x = c(-0.56, 0, 0.56), y = c(-0.5, 0.5, -0.5), theta = c(-2, -4, -6) * pi / 3))
    polylist <- list(square = square, circle = circle, diamond = diamond, triangle = triangle)
  }


  # drawbox ----- draw one person ---------
  drawbox <- function(x, y, sex, affected, status, colmat, polylist,
                      density, angle, boxw, boxh) {
    total_fractions <- 1:maxncancer
    aff_cids <- which(affected == 1)
    unknown_cids <- which(affected == -1)


    if (length(aff_cids) != 0) {
      unknown_fractions <- total_fractions[-(1:length(aff_cids))]
      for (i in seq_along(aff_cids)) {
        polygon(x + (polylist[[sex]])[[i]]$x * boxw,
          y + (polylist[[sex]])[[i]]$y * boxh,
          col = colmat[aff_cids[i]],
          border = 1, density = density[i], angle = angle[i]
        )
      }
    }
    else {
      unknown_fractions <- total_fractions
    }

    if (length(unknown_cids) != 0) {
      for (i in seq_along(unknown_cids)) {
        unf <- unknown_fractions[i]
        lab <- unknown_cids[i]
        # polygon(x + (polylist[[sex]])[[unf]]$x * boxw,
        #         y + (polylist[[sex]])[[unf]]$y * boxh, col = NA,
        #         border = col)
        midx <- x + mean(range(polylist[[sex]][[unf]]$x * boxw))
        midy <- y + mean(range(polylist[[sex]][[unf]]$y * boxh))
        # print(c(midx, midy))
        points(midx, midy,
          pch = "?",
          cex = min(1, cex * 2 / length(affected)),
          col = colmat[lab]
        )
      }
    }

    for (i in 1:maxncancer) {
      polygon(x + (polylist[[sex]])[[i]]$x * boxw,
        y + (polylist[[sex]])[[i]]$y * boxh,
        col = NA,
        border = 1
      )
    }
    if (status == 1) {
      segments(x - 0.6 * boxw, y + 1.1 * boxh, x + 0.6 *
        boxw, y - 0.1 * boxh)
    }
  }

  sex <- ifelse(x$sex == "female", 2, 1)
  for (i in 1:maxlev) {
    for (j in seq_len(plist$n[i])) {
      k <- plist$nid[i, j]
      drawbox(
        plist$pos[i, j], i, sex[k], affected[k, ],
        status[k], colmat, polylist, density, angle,
        boxw, boxh
      )
      # Omit the ID number for simplicity
      text_target <- paste(annot[k], sep = "\n")
      # text_target <- paste(id[k], annot[k], sep = "\n")
      tts <- strsplit(text_target, split = "\n")[[1]]
      for (tti in seq_along(tts)) {
        tt <- tts[tti]
        text(plist$pos[i, j], i + 0.8 * boxh + labh * 1.2 * tti, tt,
          # cex = ifelse(tti == 1, cex, cex*0.7), adj = c(0.5, 1))
          cex = ifelse(tti == 1, cex * 0.7, cex * 0.7), adj = c(0.5, 1)
        )
      }

      if (k %in% which.proband) {
        x0 <- plist$pos[i, j] - 0.3 * boxw - 0.2
        y0 <- i + 0.8 * boxh + labh
        x1 <- x0 + 0.1
        y1 <- y0 - 0.1
        length <- 0.05
        arrows(x0, y0, x1, y1, length = length)
      }
    }
  }

  maxcol <- ncol(plist$nid)
  for (i in 1:maxlev) {
    tempy <- i + boxh / 2
    if (any(plist$spouse[i, ] > 0)) {
      temp <- (1:maxcol)[plist$spouse[i, ] > 0]
      segments(
        plist$pos[i, temp] + boxw / 2, rep(
          tempy,
          length(temp)
        ), plist$pos[i, temp + 1] - boxw / 2,
        rep(tempy, length(temp))
      )
      temp <- (1:maxcol)[plist$spouse[i, ] == 2]
      if (length(temp)) {
        tempy <- tempy + boxh / 10
        segments(
          plist$pos[i, temp] + boxw / 2, rep(
            tempy,
            length(temp)
          ), plist$pos[i, temp + 1] - boxw / 2,
          rep(tempy, length(temp))
        )
      }
    }
  }
  for (i in 2:maxlev) {
    zed <- unique(plist$fam[i, ])
    zed <- zed[zed > 0]
    for (fam in zed) {
      xx <- plist$pos[i - 1, fam + 0:1]
      parentx <- mean(xx)
      who <- (plist$fam[i, ] == fam)
      if (is.null(plist$twins)) {
        target <- plist$pos[i, who]
      } else {
        twin.to.left <- (c(0, plist$twins[i, who])[1:sum(who)])
        temp <- cumsum(twin.to.left == 0)
        tcount <- table(temp)
        target <- rep(tapply(
          plist$pos[i, who], temp,
          mean
        ), tcount)
      }
      yy <- rep(i, sum(who))
      segments(plist$pos[i, who], yy, target, yy - legh)
      if (any(plist$twins[i, who] == 1)) {
        who2 <- which(plist$twins[i, who] == 1)
        temp1 <- (plist$pos[i, who][who2] + target[who2]) / 2
        temp2 <- (plist$pos[i, who][who2 + 1] + target[who2]) / 2
        yy <- rep(i, length(who2)) - legh / 2
        segments(temp1, yy, temp2, yy)
      }
      if (any(plist$twins[i, who] == 3)) {
        who2 <- which(plist$twins[i, who] == 3)
        temp1 <- (plist$pos[i, who][who2] + target[who2]) / 2
        temp2 <- (plist$pos[i, who][who2 + 1] + target[who2]) / 2
        yy <- rep(i, length(who2)) - legh / 2
        text((temp1 + temp2) / 2, yy, "?")
      }
      segments(min(target), i - legh, max(target), i - legh)
      if (diff(range(target)) < 2 * pconnect) {
        x1 <- mean(range(target))
      } else {
        x1 <- pmax(
          min(target) + pconnect,
          pmin(max(target) - pconnect, parentx)
        )
      }
      y1 <- i - legh
      if (branch == 0) {
        segments(x1, y1, parentx, (i - 1) + boxh / 2)
      } else {
        y2 <- (i - 1) + boxh / 2
        x2 <- parentx
        ydelta <- ((y2 - y1) * branch) / 2
        segments(
          c(x1, x1, x2), c(y1, y1 + ydelta, y2 - ydelta),
          c(x1, x2, x2), c(y1 + ydelta, y2 - ydelta, y2)
        )
      }
    }
  }
  arcconnect <- function(x, y) {
    xx <- seq(x[1], x[2], length = 15)
    yy <- seq(y[1], y[2], length = 15) + (seq(-7, 7))^2 / 98 -
      0.5
    lines(xx, yy, lty = 2)
  }
  uid <- unique(plist$nid)
  for (id in unique(uid[uid > 0])) {
    indx <- which(plist$nid == id)
    if (length(indx) > 1) {
      tx <- plist$pos[indx]
      ty <- ((row(plist$pos))[indx])[order(tx)]
      tx <- sort(tx)
      for (j in 1:(length(indx) - 1)) {
        arcconnect(tx[j +
          0:1], ty[j + 0:1])
      }
    }
  }
  ckall <- x$id[is.na(match(x$id, x$id[plist$nid]))]

  add_legend <- function(...) {
    opar <- par(
      fig = c(0, 1, 0, 1), oma = c(0, 0, 0, 0),
      mar = c(0, 2, 0, 0), new = TRUE
    )
    on.exit(par(opar))
    plot(0, 0, type = "n", bty = "n", xaxt = "n", yaxt = "n")
    legend(...)
  }

  cancer_legend <- add_legend("topleft",
    legend = substring(dimnames(x$affected)[[2]], 6),
    border = NA,
    col = colmat,
    # horiz = TRUE,
    cex = 0.8,
    bty = "n",
    # pch = c(rep(".", length(colmat)), "[C]"),
    fill = colmat,
    ncol = 9
  )

  if (!is.null(feature.name)) {
    fl_x <- cancer_legend$rect$left + 0.05 * boxh
    fl_y <- cancer_legend$rect$top - 0.7 * boxh #- cancer_legend$rect$h
    if (feature.name != "CurAge") {
      fl <- add_legend(
        x = fl_x - 0.09, y = fl_y,
        legend = paste0(substr(feature.name, 1, 3), "  ", feature.name),
        col = 1,
        border = NULL,
        horiz = TRUE,
        # title = "Feature Annotation",
        # pch = substr(feature.name, 1, 3),
        pt.cex = 0.8,
        # fill = NA,
        bty = "n",
        cex = 0.8
      )
    }
  }
  if (!is.null(which.proband)) {
    pl_x <- cancer_legend$rect$left
    pl_y <- cancer_legend$rect$top - boxh
    pl <- add_legend(
      x = pl_x*0.97, y = pl_y,
      legend = "Proband",
      cex = 0.8,
      bty = "n"
    )
    arrows(pl_x + 0.02, pl_y - 0.08, pl_x + 0.05, pl_y - 0.035, length = 0.03)
  }

  title(main_title)
  if (length(ckall > 0)) {
    cat("Did not plot the following people:", ckall, "\n")
  }
  if (!keep.par) {
    par(oldpar)
  }
  tmp <- match(1:length(x$id), plist$nid)
  invisible(list(
    plist = plist, x = plist$pos[tmp], y = row(plist$pos)[tmp],
    boxw = boxw, boxh = boxh, call = Call
  ))
}
bayesmendel/visPed documentation built on July 23, 2021, 5:04 a.m.