Revision 738b43f6dcce877b85c10f23dafc38e0098bd8fb authored by Daniel Luedecke on 19 February 2014, 11:32:18 UTC, committed by cran-robot on 19 February 2014, 11:32:18 UTC
1 parent 64f2fd6
Raw File
sjPlotLikert.R
# bind global variables
if(getRversion() >= "2.15.1") utils::globalVariables(c("Freq", "ypos", "Question", "Response"))



#' @title Plot likert scales as centered stacked bars
#' @name sjp.likert
#' @references \url{http://strengejacke.wordpress.com/sjplot-r-package/} \cr \cr
#'             \url{http://strengejacke.wordpress.com/2013/07/17/plotting-likert-scales-net-stacked-distributions-with-ggplot-rstats/}
#'             
#' @seealso \code{\link{sjp.stackfrq}}
#' 
#' @description Plot likert scales as centered stacked bars. "Neutral" categories
#'                (odd-numbered categories) will be removed from the plot.
#' @note Transformation of data and ggplot-code taken from
#' \url{http://statisfactions.com/2012/improved-net-stacked-distribution-graphs-via-ggplot2-trickery/}
#' 
#' @param items A data frame with each column representing one likert-item.
#' @param legendLabels A list or vector of strings that indicate the likert-scale-categories and which
#'          appear as legend text.
#' @param orderBy Indicates whether the \code{items} should be ordered by total sum of positive or negative answers.
#'          Use \code{"pos"} to order descending by sum of positive answers, \code{"neg"} for sorting descending
#'          negative answers or \code{NULL} (default) for no sorting.
#' @param reverseOrder If \code{TRUE}, the item order (positive/negative) are reversed. Default is \code{FALSE}.
#' @param dropLevels Indicates specific factor levels that should be dropped from the items
#'          before the likert scale is plotted. Default is \code{NULL}, hence all factor levels
#'          are included. Exampe to drop first factor level: \code{dropLevels=c(1)}.
#' @param weightBy A weight factor that will be applied to weight all cases from \code{items}.
#' @param weightByTitleString If a weight factor is supplied via the parameter \code{weightBy}, the diagram's title
#'          may indicate this with a remark. Default is \code{NULL}, so the diagram's title will not be modified when
#'          cases are weighted. Use a string as parameter, e.g.: \code{weightByTitleString=" (weighted)"}
#' @param hideLegend Indicates whether legend (guide) should be shown or not.
#' @param title Title of the diagram, plotted above the whole diagram panel.
#' @param titleSize The size of the plot title. Default is 1.3.
#' @param titleColor The color of the plot title. Default is \code{"black"}.
#' @param legendTitle Title of the diagram's legend.
#' @param includeN If \code{TRUE} (default), the N of each item is included into axis labels.
#' @param axisLabels.y Labels for the y-axis (the labels of the \code{items}). These parameters must
#'          be passed as list! Example: \code{axisLabels.y=list(c("Q1", "Q2", "Q3"))}
#' @param axisLabelSize The size of category labels at the axes. Default is 1.1, recommended values range
#'          between 0.5 and 3.0
#' @param axisLabelAngle.x Angle for axis-labels.
#' @param axisLabelColor User defined color for axis labels. If not specified, a default dark gray
#'          color palette will be used for the labels.
#' @param valueLabelSize The size of value labels in the diagram. Default is 4, recommended values range
#'          between 2 and 8
#' @param valueLabelColor The color of value labels in the diagram. Default is black.
#' @param breakTitleAt Wordwrap for diagram title. Determines how many chars of the title are displayed in
#'          one line and when a line break is inserted into the title.
#' @param breakLabelsAt Wordwrap for diagram labels. Determines how many chars of the category labels are displayed in 
#'          one line and when a line break is inserted.
#' @param breakLegendTitleAt Wordwrap for diagram legend title. Determines how many chars of the legend's title 
#'          are displayed in one line and when a line break is inserted.
#' @param breakLegendLabelsAt Wordwrap for diagram legend labels. Determines how many chars of the legend labels are 
#'          displayed in one line and when a line break is inserted.
#' @param gridRange Sets the limit of the x-axis-range. Default is 1, so the x-scale ranges
#'          from zero to 100 percent on both sides from the center. Valid values 
#'          range from 0 (0 percent) to 1 (100 percent).
#' @param gridBreaksAt Sets the breaks on the y axis, i.e. at every n'th position a major
#'          grid is being printed. Valid values range from 0 to 1.
#' @param diagramMargins If \code{TRUE} (default), the diagram has margins, i.e. the y-axis is not exceeded
#'          to the diagram's boundaries.
#' @param barWidth Width of bars. Recommended values for this parameter are from 0.4 to 1.5
#' @param barColor User defined color for bars.
#'          If not specified (\code{NULL}), a default red-green color palette for four(!) categories will be used 
#'          for the bar charts. You can use pre-defined color-sets that are independent from the amount of categories:
#'          \itemize{
#'          \item If barColor is \code{"brown"}, a brown-marine-palette will be used.
#'          \item If barColor is \code{"violet"}, a violet-green palette will be used.
#'          \item If barColor is \code{"pink"}, a pink-green palette will be used.
#'          \item If barColor is \code{"brewer"}, use the \code{colorPalette} parameter to specify a palette of the color brewer.
#'          }
#'          Else specify your own color values as vector (e.g. \code{barColor=c("darkred", "red", "green", "darkgreen")})
#' @param colorPalette If \code{barColor} is \code{"brewer"}, specify a color palette from the color brewer here. All color brewer 
#'          palettes supported by ggplot are accepted here.
#' @param barAlpha Specify the transparancy (alpha value) of bars.
#' @param borderColor User defined color of whole diagram border (panel border).
#' @param barOutline If \code{TRUE}, each bar gets a colored outline. Default is \code{FALSE}.
#' @param outlineColor The color of the bar outline. Only applies, if \code{barOutline} is set to \code{TRUE}.
#' @param majorGridColor Specifies the color of the major grid lines of the diagram background.
#' @param minorGridColor Specifies the color of the minor grid lines of the diagram background.
#' @param hideGrid.x If \code{TRUE}, the x-axis-gridlines are hidden. Default if \code{FALSE}.
#' @param hideGrid.y If \code{TRUE}, the y-axis-gridlines are hidden. Default if \code{FALSE}.
#' @param axisColor User defined color of axis border (y- and x-axis, in case the axes should have different colors than
#'          the diagram border).
#' @param axisTitle.x A label for the x axis. Useful when plotting histograms with metric scales where no category labels
#'          are assigned to the x axis.
#' @param axisTitle.y A label for the y axis. Useful when plotting histograms with metric scales where no category labels
#'          are assigned to the y axis.
#' @param axisTitleColor The color of the x and y axis labels. refers to \code{axisTitle.x} and \code{axisTitle.y},
#'          not to the tick mark or category labels.
#' @param axisTitleSize The size of the x and y axis labels. refers to \code{axisTitle.x} and \code{axisTitle.y},
#'          not to the tick mark or category labels.
#' @param showValueLabels Whether counts and percentage values should be plotted to each bar
#' @param jitterValueLabels If \code{TRUE}, the value labels on the bars will be "jittered", i.e. they have
#'          alternating vertical positions to avoid overlapping of labels in case bars are
#'          very short. Default is \code{FALSE}.
#' @param showItemLabels Whether x axis text (category names) should be shown or not
#' @param showTickMarks Whether tick marks of axes should be shown or not
#' @param showSeparatorLine If \code{TRUE}, a line is drawn to visually "separate" each bar in the diagram.
#' @param separatorLineColor The color of the separator line. Only applies, if \code{showSeparatorLine} is \code{TRUE}
#' @param separatorLineSize The size of the separator line. only applies, if \code{showSeparatorLine} is \code{TRUE}
#' @param legendPos The position of the legend. Default is \code{"right"}. Use one of the following values:
#'          \code{"right"}, \code{"left"}, \code{"bottom"}, \code{"top"}.
#' @param legendSize The size of the legend.
#' @param legendBorderColor The border color of the legend.
#' @param legendBackColor The background color of the legend.
#' @param theme Specifies the diagram's background theme. Default (parameter \code{NULL}) is a gray 
#'          background with white grids.
#'          \itemize{
#'          \item Use \code{"bw"} for a white background with gray grids
#'          \item \code{"classic"} for a classic theme (black border, no grids)
#'          \item \code{"minimal"} for a minimalistic theme (no border,gray grids) or 
#'          \item \code{"none"} for no borders, grids and ticks.
#'          }
#'          The ggplot-object can be returned with \code{returnPlot} set to \code{TRUE} in order to further
#'          modify the plot's theme.
#' @param flipCoordinates If \code{TRUE}, the x and y axis are swapped.
#' @param returnPlot If \code{TRUE}, the ggplot-object with the complete plot will be returned (and not plotted).
#'          Default is \code{FALSE}, hence the ggplot object will be plotted, not returned.
#' @return The ggplot-object with the complete plot in case \code{returnPlot} is \code{TRUE}.
#' 
#' @examples
#' # prepare data for dichotomous likert scale, 5 items
#' likert_2 <- data.frame(as.factor(sample(1:2, 500, replace=TRUE, prob=c(0.3,0.7))),
#'                        as.factor(sample(1:2, 500, replace=TRUE, prob=c(0.6,0.4))),
#'                        as.factor(sample(1:2, 500, replace=TRUE, prob=c(0.25,0.75))),
#'                        as.factor(sample(1:2, 500, replace=TRUE, prob=c(0.9,0.1))),
#'                        as.factor(sample(1:2, 500, replace=TRUE, prob=c(0.35,0.65))))
#' # create labels
#' levels_2 <- list(c("Disagree", "Agree"))
#'                        
#' # prepare data for 4-category likert scale, 5 items
#' likert_4 <- data.frame(as.factor(sample(1:4, 500, replace=TRUE, prob=c(0.2,0.3,0.1,0.4))),
#'                        as.factor(sample(1:4, 500, replace=TRUE, prob=c(0.5,0.25,0.15,0.1))),
#'                        as.factor(sample(1:4, 500, replace=TRUE, prob=c(0.25,0.1,0.4,0.25))),
#'                        as.factor(sample(1:4, 500, replace=TRUE, prob=c(0.1,0.4,0.4,0.1))),
#'                        as.factor(sample(1:4, 500, replace=TRUE, prob=c(0.35,0.25,0.15,0.25))))
#' # create labels
#' levels_4 <- list(c("Strongly disagree", "Disagree", "Agree", "Strongly Agree"))
#' 
#' # prepare data for 6-category likert scale, 5 items
#' likert_6 <- data.frame(
#'                as.factor(sample(1:6, 500, replace=TRUE, prob=c(0.2,0.1,0.1,0.3,0.2,0.1))),
#'                as.factor(sample(1:6, 500, replace=TRUE, prob=c(0.15,0.15,0.3,0.1,0.1,0.2))),
#'                as.factor(sample(1:6, 500, replace=TRUE, prob=c(0.2,0.25,0.05,0.2,0.2,0.2))),
#'                as.factor(sample(1:6, 500, replace=TRUE, prob=c(0.2,0.1,0.1,0.4,0.1,0.1))),
#'                as.factor(sample(1:6, 500, replace=TRUE, prob=c(0.1,0.4,0.1,0.3,0.05,0.15))))
#' # create labels
#' levels_6 <- list(c("Very strongly disagree", "Strongly disagree", "Disagree",
#'                    "Agree", "Strongly Agree", "Very strongly agree"))
#' 
#' # create item labels
#' items <- list(c("Q1", "Q2", "Q3", "Q4", "Q5"))
#' 
#' # plot dichotomous likert scale, ordered by "negative" values
#' sjp.likert(likert_2, legendLabels=levels_2, axisLabels.y=items, orderBy="neg")
#' 
#' # plot 4-category-likert-scale, no order
#' sjp.likert(likert_4, legendLabels=levels_4, axisLabels.y=items)
#' 
#' # plot 4-category-likert-scale, ordered by positive values,
#' # in brown color scale and with jittered value labels
#' sjp.likert(likert_6, legendLabels=levels_6, barColor="brown",
#'            axisLabels.y=items, orderBy="pos", jitterValueLabels=TRUE)
#' 
#' @import ggplot2
#' @importFrom scales brewer_pal
#' @importFrom plyr ddply
#' @export
sjp.likert <- function(items,
                        legendLabels,
                        orderBy=NULL,
                        reverseOrder=FALSE,
                        dropLevels=NULL,
                        weightBy=NULL,
                        weightByTitleString=NULL,
                        hideLegend=FALSE,
                        title=NULL, 
                        titleSize=1.3,
                        titleColor="black",
                        legendTitle=NULL,
                        includeN=TRUE,
                        axisLabels.y=NULL,
                        axisLabelSize=1.1,
                        axisLabelAngle.x=0, 
                        axisLabelColor="gray30", 
                        valueLabelSize=4,
                        valueLabelColor="black",
                        breakTitleAt=50, 
                        breakLabelsAt=30, 
                        breakLegendTitleAt=30, 
                        breakLegendLabelsAt=28,
                        gridRange=1,
                        gridBreaksAt=0.2,
                        diagramMargins=TRUE,
                        barWidth=0.5, 
                        barColor=NULL,
                        colorPalette="GnBu",
                        barAlpha=1,
                        borderColor=NULL, 
                        axisColor=NULL, 
                        barOutline=FALSE, 
                        outlineColor="black", 
                        majorGridColor=NULL,
                        minorGridColor=NULL,
                        hideGrid.x=FALSE,
                        hideGrid.y=FALSE,
                        axisTitle.x=NULL,
                        axisTitle.y=NULL,
                        axisTitleColor="black",
                        axisTitleSize=1.3,
                        theme=NULL,
                        showTickMarks=FALSE,
                        showValueLabels=TRUE,
                        jitterValueLabels=FALSE,
                        showItemLabels=TRUE,
                        showSeparatorLine=FALSE,
                        separatorLineColor="grey80",
                        separatorLineSize=0.3,
                        legendPos="right",
                        legendSize=1,
                        legendBorderColor="white",
                        legendBackColor="white",
                        flipCoordinates=TRUE,
                        returnPlot=FALSE) {
  # --------------------------------------------------------
  # unlist labels
  # --------------------------------------------------------
  # Help function that unlists a list into a vector
  unlistlabels <- function(lab) {
    dummy <- unlist(lab)
    labels <- c()
    for (i in 1:length(dummy)) {
      labels <- c(labels, as.character(dummy[i]))
    }
    return (labels)
  }
  if (!is.null(axisLabels.y) && is.list(axisLabels.y)) {
    axisLabels.y <- unlistlabels(axisLabels.y)
  }
  if (!is.null(legendLabels) && is.list(legendLabels)) {
    legendLabels <- unlistlabels(legendLabels)
  }

  
  # --------------------------------------------------------
  # transform data frame content into factor
  # --------------------------------------------------------
  # check whether data in data frame are atomic, numeric etc.
  if (!is.factor(items[[1]])) {
    # iterate all data columns (items, variables)
    for (w in 1:ncol(items)) {
      # transform to factor
      items[,w] <- as.factor(items[,w])
      # check whether factor levels should be reversed
      if (reverseOrder) {
        # if yes, reverse levels. since not all variables/items may contain all possible factor
        # levels (i.e. some answer categories are missing), we determine the amount of factor
        # levels by the length of legend labels. The legend labels indicate the correct amount
        # of categories from the likert scale ("agree" to "disagree")
        items[,w] = factor(items[,w],levels(items[,w])[c(seq(from=length(legendLabels), to=1))])
      }
      # rename factor levels so they are quasi-numeric
      levels(items[,w]) <- c(paste(seq(1:length(legendLabels))))
    }
  }

  
  # --------------------------------------------------------
  # reverse legend labels, if factor levels should be reversed
  # --------------------------------------------------------
  if (!is.null(legendLabels) && reverseOrder) {
    legendLabels <- rev(legendLabels)
  }
  
  
  # --------------------------------------------------------
  # Drop factor levels, if requested
  # --------------------------------------------------------
  # check whether certain factor levels should be dropped
  if (!is.null(dropLevels)) {
    # iterate all data columns (items, variables)
    for (w in 1:ncol(items)) {
      # iterate all levels that should be dropped
      for (v in 1:length(dropLevels)) {
        # remove factor level
        items[,w][items[,w]==dropLevels[v]] <- NA
        items[,w] <- factor(items[,w])
      }
    }
    # now we have to remove the related legendLabels as well
    # therefor, order droplevels decreasing
    dropLevels <- dropLevels[order(dropLevels, decreasing=TRUE)]
    for (u in length(dropLevels):1) {
      # check whether droplevel is inside vector indices
      if (dropLevels[u]<=length(legendLabels)) {
        # remove label of droplevel
        legendLabels <- legendLabels[legendLabels!=legendLabels[dropLevels[u]]]
      }
    }
  }

    
  # --------------------------------------------------------
  # Check whether N of each item should be included into
  # axis labels
  # --------------------------------------------------------
  if (includeN && !is.null(axisLabels.y)) {
    for (i in 1:length(axisLabels.y)) {
      axisLabels.y[i] <- paste(axisLabels.y[i], sprintf(" (n=%i)", length(na.omit(items[,i]))), sep="")
    }
  }
  
  
  # ---------------------------------------------------------------------------------------------
  # The following part which does the transformation of factor levels into negative and positive
  # answers was taken from
  # http://statisfactions.com/2012/improved-net-stacked-distribution-graphs-via-ggplot2-trickery/
  #
  # slightly modifications were made by including a weight-factor and calculating the cumulative
  # sums of percentages for the value label positioninh
  # ---------------------------------------------------------------------------------------------
  # retrieve levels of items
  # --------------------------------------------------------
  all_levels <- levels(items[[1]])
  n <- length(all_levels)
  # --------------------------------------------------------
  # Reverse order of columns (to make ggplot2 output look right after coord_flip)
  # --------------------------------------------------------
  items <- items[length(items):1]
  # --------------------------------------------------------
  # Identify middle and "negative" levels
  # --------------------------------------------------------
  if(n %% 2 == 1) {
    neutral <- all_levels[ceiling(n/2)]
  }
  else {
    neutral <- NULL
  }
  # --------------------------------------------------------
  # split factor levels according to "agree" and "disagree"
  # --------------------------------------------------------
  negatives <- all_levels[1:floor(n/2)]
  positives <- setdiff(all_levels, c(negatives, neutral))

  
  # --------------------------------------------------------
  # remove neutral, summarize as proportion
  # --------------------------------------------------------
  listall <- lapply(names(items), function(y) {
    # column <- (na.omit(items[[y]]))
    column <- items[[y]]
    if (is.null(weightBy)) {
      out <- data.frame(Question = y, prop.table(table(column)))
    }
    else {
      out <- data.frame(Question = y, prop.table(round(xtabs(weightBy ~ column),0)))
    }
    names(out) <- c("Question", "Response", "Freq")
    
    if(!is.null(neutral)) {
      out <- out[out$Response != neutral,]
    }
    out
  })
  dfall <- do.call(rbind, listall)

  
  # --------------------------------------------------------
  # split by positive/negative, and check whether factor
  # levels should be reversed
  # --------------------------------------------------------
  pos <- dfall[dfall$Response %in% positives,]
  neg <- dfall[dfall$Response %in% negatives,]

  
  # --------------------------------------------------------
  # add half of Percentage values as new y-position for stacked bars
  # --------------------------------------------------------
  pos = ddply(pos, "Question", transform, ypos = cumsum(Freq) - 0.5*Freq)
  neg = ddply(neg, "Question", transform, ypos = rev(cumsum(rev(Freq)) - 0.5*rev(Freq)))

  
  # --------------------------------------------------------
  # Negate the frequencies of negative responses, reverse order
  # --------------------------------------------------------
  neg$Freq <- -neg$Freq
  neg$ypos <- -neg$ypos
  neg$Response <- ordered(neg$Response, levels = rev(levels(neg$Response)))
  # save numbers of items we have. needed later for calculating the
  # sorting of items
  questionCount <- nrow(pos)/(length(legendLabels)/2)

  # --------------------------------------------------------
  # order items according to sum of positive or negative answers
  # given.
  # --------------------------------------------------------
  if (!is.null(orderBy)) {
    if (orderBy=="pos") {
      pos <- ddply(pos, "Question", transform, ytotal = sum(Freq))
      orderGroupedItems <- order(pos$ytotal)
    }
    else {
      neg <- ddply(neg, "Question", transform, ytotal = sum(abs(Freq)))
      orderGroupedItems <- order(neg$ytotal)
    }
    # ------------
    # in "orderGroupedItems", we have now a "grouped" order. each "group" consists of data 
    # with equal count to the positive or negative amount of legend labels (i.e.
    # half of the amount of all legendLabels).
    # The amount of groups, however, is related to the amount of items we have.
    # Example: We have 5 items with 6 categories (legendLabels) each,
    # for instance "very strong disagree", "strong disagree", "disagree", "agree",
    # "strong agree", "very strong agree".
    # Now "orderGroupedItems" consists of five groups (= 5 items) and each "group" has three
    # data rows (6 categories divided by 2 (pos and neg)).
    # So we have 15 data rows.
    #
    # Example: "pos" after applying ddply-function:
    #     Question Response  Freq  ypos ytotal
    #     1        Q5        4 0.266 0.133  0.442
    #     2        Q5        5 0.050 0.291  0.442
    #     3        Q5        6 0.126 0.379  0.442
    #     4        Q4        4 0.406 0.203  0.588
    #     5        Q4        5 0.084 0.448  0.588
    #     6        Q4        6 0.098 0.539  0.588
    #     7        Q3        4 0.200 0.100  0.570
    #     8        Q3        5 0.196 0.298  0.570
    #     9        Q3        6 0.174 0.483  0.570
    #     10       Q2        4 0.094 0.047  0.376
    #     11       Q2        5 0.078 0.133  0.376
    #     12       Q2        6 0.204 0.274  0.376
    #     13       Q1        4 0.294 0.147  0.596
    #     14       Q1        5 0.216 0.402  0.596
    #     15       Q1        6 0.086 0.553  0.596
    #
    # Now "orderGroupedItems" looks like following:
    # [1] 10 11 12  1  2  3  7  8  9  4  5  6 13 14 15
    # 
    # So we have the order from lowest sum of positive or negative
    # answer frequencies to highest, but three times each. for ordering
    # the legend labels, we have to transform "orderGroupedItems", see below!
    # ------------
    pos$Freq <- pos$Freq[orderGroupedItems]
    neg$Freq <- neg$Freq[orderGroupedItems]
    pos$ypos <- pos$ypos[orderGroupedItems]
    neg$ypos <- neg$ypos[orderGroupedItems]
    
    # since "orderGroupedItems" has numbers from 1 to (items * legendLabels/2) - i.e. 1 to 15
    # in this example -, we need to know, which "group" belongs to which item. we do
    # this by dividing these numbers by "amount of positive / negative legendLabels",
    # i.e. "orderGroupedItems" will be divided by (length of legendLabels / 2).
    orderRelatedItems <- c(ceiling(orderGroupedItems/(length(legendLabels)/2)))
    
    # now we have the in "orderUniqueItems" the items assigned to each row of the data frame
    # pos resp. neg:
    # [1] 4 4 4 1 1 1 3 3 3 2 2 2 5 5 5
    # Next, we just need each item number once, so extract the unique values
    orderUniqueItems <- c(unique(orderRelatedItems))
    
    # now we have in "oderUniqueNumbers" the items with the lowest frequencies
    # to highest frequencies, with each number pointing the question position, beginng
    # from the end.
    # Example, how "oderUniqueNumbers" looks like:
    # [1] 4 1 3 2 5
    # That means, when we have 5 questions / items, the 4th question/item, counted 
    # from the end, is question/item 2.
    # Thus, question/item 2 has the lowest total frequencies (first position in
    # "oderUniqueNumbers", last position in order).
    # The second number in "oderUniqueNumbers" is "1", i.e. the first question from the
    # end is question 5, which appears at position 2 with the lowest total frequencies.
    # 
    # So we now have to switch index from (end to beginning) to (beginning to end)
    # and reverse the order to start with highest frequencies.
    orderUniqueItems <- rev(1+questionCount-orderUniqueItems)
    # If axisLabels.y were not defined, simply set numbers from 1 to
    # amount of items
    if (is.null(axisLabels.y)) {
      axisLabels.y <- c(1:length(items))
    }
    # The result in "orderUniqueItems" now is
    # [1] 1 4 3 5 2
    # with this we can order the axis labels (item/question labels)
    axisLabels.y <- axisLabels.y[orderUniqueItems]
  }
  # --------------------------------------------------------
  # Caculate vertical adjustment to avoid overlapping labels
  # --------------------------------------------------------
  jnvert <- rep(c(-0.1,1.1), length.out=nrow(neg))
  jpvert <- rep(c(1.1,-0.1), length.out=nrow(pos))
  # --------------------------------------------------------
  # Prepare and trim legend labels to appropriate size
  # --------------------------------------------------------
  # wrap legend text lines
  legendLabels <- sju.wordwrap(legendLabels, breakLegendLabelsAt)
  # check whether we have a title for the legend
  if (!is.null(legendTitle)) {
    # if yes, wrap legend title line
    legendTitle <- sju.wordwrap(legendTitle, breakLegendTitleAt)
  }
  # check length of diagram title and split longer string at into new lines
  # every 50 chars
  if (!is.null(title)) {
    # if we have weighted values, say that in diagram's title
    if (!is.null(weightByTitleString)) {
      title <- paste(title, weightByTitleString, sep="")
    }
    title <- sju.wordwrap(title, breakTitleAt)
  }
  # check length of x-axis-labels and split longer strings at into new lines
  # every 10 chars, so labels don't overlap
  if (!is.null(axisLabels.y)) {
    axisLabels.y <- sju.wordwrap(axisLabels.y, breakLabelsAt)
  }
  
  
  # --------------------------------------------------------
  # define vertical position for labels
  # --------------------------------------------------------
  if (flipCoordinates) {
    # if we flip coordinates, we have to use other parameters
    # than for the default layout
    vert <- 0.35
  }
  else {
    vert <- waiver()
  }
  # --------------------------------------------------------
  # check whether bars should have an outline
  # --------------------------------------------------------
  if (!barOutline) {
    outlineColor <- waiver()
  }

  
  # --------------------------------------------------------
  # Set theme and default grid colours. grid colours
  # might be adjusted later
  # --------------------------------------------------------
  hideGridColor <- c("white")
  if (is.null(theme)) {
    ggtheme <- theme_gray()
    hideGridColor <- c("gray90")
  }
  else if (theme=="bw") {
    ggtheme <- theme_bw()
  }
  else if (theme=="classic") {
    ggtheme <- theme_classic()
  }
  else if (theme=="minimal") {
    ggtheme <- theme_minimal()
  }
  else if (theme=="none") {
    ggtheme <- theme_minimal()
    majorGridColor <- c("white")
    minorGridColor <- c("white")
    showTickMarks <-FALSE
  }
  # --------------------------------------------------------
  # set diagram margins
  # --------------------------------------------------------
  if (diagramMargins) {
    expgrid <- waiver()
  }
  else {
    expgrid <- c(0,0)
  }
  # --------------------------------------------------------
  # Hide or show Tick Marks and Category Labels (x axis text) 
  # --------------------------------------------------------
  if (!showTickMarks) {
    ggtheme <- ggtheme + theme(axis.ticks = element_blank())
  }
  if (!showItemLabels) {
    axisLabels.y <- c("")
  }
  else {
    axisLabels.y <- rev(axisLabels.y)
  }
  # --------------------------------------------------------
  # Prepare fill colors
  # --------------------------------------------------------
  ln <- length(negatives)
  if (is.null(barColor)) {
    if (length(legendLabels)==2) {
      cols <- c('#AA1111', '#11AA11')
    }
    else if (length(legendLabels)==4) {
      cols <- c('#AA1111', '#BB6666', '#66BB66', '#11AA11')
    }
    else {
      cols <- c('#AA1111', '#BB6666', '#CC9999', '#99CC99', '#66BB66', '#11AA11')
    }
    scalecolors <- scale_fill_manual(labels=legendLabels, values=cols)
  }
  else if (barColor=="violet") {
    cp <- brewer_pal(palette="PRGn")(2*ln+1)
    cols <- cp[c(1:ln,(ln+2):((2*ln)+1))]
    scalecolors <- scale_fill_manual(labels=legendLabels, values=cols)
  }
  else if (barColor=="brown") {
    cp <- brewer_pal(palette="BrBG")(2*ln+1)
    cols <- cp[c(1:ln,(ln+2):((2*ln)+1))]
    scalecolors <- scale_fill_manual(labels=legendLabels, values=cols)
  }
  else if (barColor=="pink") {
    cp <- brewer_pal(palette="PiYG")(2*ln+1)
    cols <- cp[c(1:ln,(ln+2):((2*ln)+1))]
    scalecolors <- scale_fill_manual(labels=legendLabels, values=cols)
  }
  else if (barColor=="brewer") {
    # remember to specify the "colorPalette" if you use "brewer" as "barColor"
    scalecolors <- scale_fill_brewer(palette=colorPalette, labels=legendLabels)
  }
  else {
    scalecolors <- scale_fill_manual(values=barColor, labels=legendLabels)
  }
  # --------------------------------------------------------
  # Set value labels
  # --------------------------------------------------------
  if (showValueLabels) {
    if (jitterValueLabels) {
      ggvaluelabels_lo <-  geom_text(data=neg, aes(y=ypos, label=sprintf("%.01f%%", -100*Freq)),
                                     size=valueLabelSize,
                                     vjust=jnvert,
                                     # hjust=hort,
                                     colour=valueLabelColor)
      ggvaluelabels_hi <-  geom_text(data=pos, aes(y=ypos, label=sprintf("%.01f%%", 100*Freq)),
                                     size=valueLabelSize,
                                     vjust=jpvert,
                                     # hjust=hort,
                                     colour=valueLabelColor)
    }
    else {
      ggvaluelabels_lo <-  geom_text(data=neg, aes(y=ypos, label=sprintf("%.01f%%", -100*Freq)),
                                     size=valueLabelSize,
                                     vjust=vert,
                                     # hjust=hort,
                                     colour=valueLabelColor)
      ggvaluelabels_hi <-  geom_text(data=pos, aes(y=ypos, label=sprintf("%.01f%%", 100*Freq)),
                                     size=valueLabelSize,
                                     vjust=vert,
                                     # hjust=hort,
                                     colour=valueLabelColor)
    }
  }
  else {
    ggvaluelabels_lo <-  geom_text(label="")
    ggvaluelabels_hi <-  geom_text(label="")
  }
  # --------------------------------------------------------
  # Set up grid breaks
  # --------------------------------------------------------
  gridbreaks <- c(seq(-gridRange, gridRange, by=gridBreaksAt))
  gridlabs <- paste0(c(abs(round(100*gridbreaks))),"%")
  # --------------------------------------------------------
  # Set up grid colours
  # --------------------------------------------------------
  majorgrid <- NULL
  minorgrid <- NULL
  if (!is.null(majorGridColor)) {
    majorgrid <- element_line(colour=majorGridColor)
  }
  if (!is.null(minorGridColor)) {
    minorgrid <- element_line(colour=minorGridColor)
  }
  hidegrid <- element_line(colour=hideGridColor)
  # --------------------------------------------------------
  # start plot
  # --------------------------------------------------------
  baseplot <- ggplot() +
    aes(Question, Freq, fill = Response, order = Response) + 
    geom_bar(data = neg, stat = "identity", colour=outlineColor, width=barWidth, alpha=barAlpha) +
    geom_bar(data = pos, stat = "identity", colour=outlineColor, width=barWidth, alpha=barAlpha) +
    geom_hline(yintercept=0, colour="white")
  # --------------------------------------------------------
  # check whether bars should be visually separated by an 
  # additional separator line
  # --------------------------------------------------------
  if (showSeparatorLine) {
    baseplot <- baseplot +
      geom_vline(x=c(seq(1.5, length(items), by=1)), size=separatorLineSize, colour=separatorLineColor)
  }
  # --------------------------------------------------------
  # Hide or show Legend
  # --------------------------------------------------------
  if (hideLegend) {
    # remove guide / legend
    baseplot <- baseplot + guides(fill=FALSE)
  }
  baseplot <- baseplot +
    # show absolute and percentage value of each bar.
    ggvaluelabels_hi +
    ggvaluelabels_lo +
    # no additional labels for the x- and y-axis, only diagram title
    labs(title=title, x=axisTitle.x, y=axisTitle.y, fill=legendTitle) +
    # print value labels to the x-axis.
    # If parameter "axisLabels.y" is NULL, the category numbers (1 to ...) 
    # appear on the x-axis
    scale_x_discrete(labels=axisLabels.y) +
    # set Y-axis, depending on the calculated upper y-range.
    # It either corresponds to the maximum amount of cases in the data set
    # (length of var) or to the highest count of var's categories.
    scale_y_continuous(breaks=gridbreaks, limits=c(-gridRange, gridRange), expand=expgrid, labels=gridlabs) +
    scalecolors  +
    ggtheme
  # check whether coordinates should be flipped, i.e.
  # swap x and y axis
  if (flipCoordinates) {
    baseplot <- baseplot + coord_flip()
  }
  # set font size for axes.
  baseplot <- baseplot + 
    theme(axis.text = element_text(size=rel(axisLabelSize), colour=axisLabelColor), 
          axis.title = element_text(size=rel(axisTitleSize), colour=axisTitleColor), 
          axis.text.x = element_text(angle=axisLabelAngle.x))
  # --------------------------------------
  # set position and size of legend
  # --------------------------------------
  if (!hideLegend) {
    baseplot <- baseplot + 
      theme(legend.position = legendPos,
            legend.text = element_text(size=rel(legendSize)),
            legend.background = element_rect(colour=legendBorderColor, fill=legendBackColor))
  }
  # the panel-border-property can only be applied to the bw-theme
  if (!is.null(borderColor)) {
    if (!is.null(theme) && theme=="bw") {
      baseplot <- baseplot + 
        theme(panel.border = element_rect(colour=borderColor))
    }
    else {
      cat("\nParameter 'borderColor' can only be applied to 'bw' theme.\n")
    }
  }
  if (!is.null(axisColor)) {
    baseplot <- baseplot + 
      theme(axis.line = element_line(colour=axisColor))
  }
  if (!is.null(minorgrid)) {
    baseplot <- baseplot + 
      theme(panel.grid.minor = minorgrid)
  }
  if (!is.null(majorgrid)) {
    baseplot <- baseplot + 
      theme(panel.grid.major = majorgrid)
  }
  if (hideGrid.x) {
    baseplot <- baseplot + 
      theme(panel.grid.major.x = hidegrid,
            panel.grid.minor.x = hidegrid)
  }
  if (hideGrid.y) {
    baseplot <- baseplot + 
      theme(panel.grid.major.y = hidegrid,
            panel.grid.minor.y = hidegrid)
  }
  # ---------------------------------------------------------
  # Check whether ggplot object should be returned or plotted
  # ---------------------------------------------------------
  if (returnPlot) {
    return(baseplot)
  }
  else {
    plot(baseplot)
  }
}
back to top