We Are All Monica
We Are All Monica

Reputation: 13344

Adding a manual legend in ggplot2?

I'm doing a comparison chart of two different estimates of the same time series data. I'm filling the area between the two series in green if the original estimate is more than the latest estimate, and red otherwise.

I've got that part working, but I'd like to add a legend for the fill colors. I tried scale_fill_manual towards the bottom of the code, but it doesn't seem to be doing anything?

Here's the code:

library(ggplot2)
library(scales)
library(colorspace)

# Return a polygon that only plots between yLower and yUpper when yLower is
# less than yUpper.
getLowerPolygon = function(x, yLower, yUpper) {
    # Create the table of coordinates
    poly = data.frame(
        x = numeric(),
        y = numeric())

    lastReversed = (yUpper[1] < yLower[1])
    for (r in 1:length(x)) {
        reversed = (yUpper[r] < yLower[r])
        if (reversed != lastReversed) {
            # Between points r-1 and r, the series intersected, so we need to
            # change the polygon from visible to invisible or v.v.  In either
            # case, just add the intersection between those two segments to the
            # polygon.  Algorithm from:
            # https://en.wikipedia.org/wiki/Line-line_intersection
            # First line: x1,y1 - x2,y2
            x1 = x[r-1]
            y1 = yLower[r-1]
            x2 = x[r]
            y2 = yLower[r]
            # Second line: x3,y3 - x4,y4
            x3 = x[r-1]
            y3 = yUpper[r-1]
            x4 = x[r]
            y4 = yUpper[r]
            # Calculate determinants
            xy12 = det(matrix(c(x1, y1, x2, y2), ncol = 2))
            xy34 = det(matrix(c(x3, y3, x4, y4), ncol = 2))
            x12  = det(matrix(c(x1,  1, x2,  1), ncol = 2))
            x34  = det(matrix(c(x3,  1, x4,  1), ncol = 2))
            y12  = det(matrix(c(y1,  1, y2,  1), ncol = 2))
            y34  = det(matrix(c(y3,  1, y4,  1), ncol = 2))
            # Calculate fraction pieces
            xn = det(matrix(c(xy12, x12, xy34, x34), ncol = 2))
            yn = det(matrix(c(xy12, y12, xy34, y34), ncol = 2))
            d  = det(matrix(c(x12 , y12,  x34, y34), ncol = 2))
            # Calculate intersection
            xi = xn / d
            yi = yn / d
            # Add the point
            poly[nrow(poly)+1,] = c(xi, yi)
        }
        lastReversed = reversed
        # http://stackoverflow.com/questions/2563824
        poly[nrow(poly)+1,] = c(x[r], min(yLower[r], yUpper[r]))
    }

    poly = rbind(poly, data.frame(
        x = rev(x),
        y = rev(yUpper)))

    return(poly)
}

getComparisonPlot = function(data, title, lower_name, upper_name,
                         x_label, y_label, legend_title = '') {

    lightGreen = '#b0dd8d'
    lightRed   = '#fdba9a'

    darkGray = RGB(.8, .8, .8)
    midGray  = RGB(.5, .5, .5)

    plot = ggplot(data, aes(x = x))

    plot = plot + geom_polygon(
        aes(x = x, y = y),
        data = data.frame(
            x = c(data$x, rev(data$x)),
            y = c(data$yLower, rev(data$yUpper))
        ),
        fill = lightRed)

    coords = getLowerPolygon(data$x, data$yLower, data$yUpper)

    plot = plot + geom_polygon(
        aes(x = x, y = y),
        data = coords,
        fill = lightGreen)

    plot = plot + geom_line(
        aes(y = yUpper, color = 'upper'),
        size = 0.5)

    plot = plot + geom_line(
        aes(y = yLower, color = 'lower'),
        size = 0.5)

    plot = plot +
        ggtitle(paste(title, '\n', sep='')) +
        xlab(x_label) +
        ylab(y_label) +
        scale_y_continuous(labels = comma)

    # http://stackoverflow.com/a/10355844/106302
    plot = plot + scale_color_manual(
        name   = legend_title,
        breaks = c('upper' , 'lower'),
        values = c('gray20', 'gray50'),
        labels = c(upper_name, lower_name))

    plot = plot + scale_fill_manual(
        name   = 'Margin',
        breaks = c('upper', 'lower'),
        values = c(lightGreen, lightRed),
        labels = c('Over', 'Under'))

    return(plot)
}

print(getComparisonPlot(
    data = data.frame(
        x = 1:20,
        yLower = 1:20 %% 5 + 2,
        yUpper = 1:20 %% 7
    ),
    title = 'Comparison Chart',
    lower_name = 'Latest',
    upper_name = 'Original',
    x_label = 'X axis',
    y_label = 'Y axis',
    legend_title = 'Thing'
))

Here's an image of the chart, I think it is a cool technique:

Comparison Chart

I'm also open to any other suggestions for improving my ggplot code.

Upvotes: 2

Views: 9417

Answers (1)

Athos
Athos

Reputation: 660

GGplot need you to map polygons fill aesthetic to some variable. OR, in this case, it need just you to "label" the types of polygons (i.e. 'upper' and 'lower'). You do this by passing a string with the respective label for the fill aesthetic of geom_polygon(). What you are doing is passing a giving colour for each polygon and not mapping to anything that the ggplot will understand. It's kind of a "hard coded colour" =P.

Well, here are the changes inside getComparisonPlot:

plot = plot + geom_polygon(
    aes(x = x, y = y, fill = "upper"),
    data = coords)

plot = plot + geom_polygon(
    aes(x = x, y = y, fill = "lower"),
    data = data.frame(
      x = c(data$x, rev(data$x)),
      y = c(data$yLower, rev(data$yUpper))
    ))

One more thing. Note that the strings passed to fill aesthetic coincides with the breaks passed to the scale_fill_manual. It is necessary to make the legend map things right.

plot = plot + scale_fill_manual(
    name   = 'Margin',
    breaks = c('upper', 'lower'), # <<< corresponds to fill aesthetic labels
    values = c(lightGreen, lightRed),
    labels = c('Over', 'Under'))

Result:

enter image description here

hope it helps.

Upvotes: 7

Related Questions