Hot questions for Using Ggplot2 in ggforce

Question:

I want to draw a rectangular annotation that will span over the facet borders in ggplot.

library(ggplot2)
myPlot <- ggplot(mpg, aes(displ, hwy)) + 
    geom_point() + 
    facet_grid(class ~ .)

# add annotation
myPlot +
  annotate("rect", xmin = 3, xmax = 4, ymin = -Inf, ymax = Inf, fill = "green", alpha = 1/5)

What I have so far:

I'd like to draw 1 large rectangle that spans the facet edges like so:

Is there a way to do this with built-in ggplot2 code or with ggforce or do I have to mess around with grid? My ideal use-case would still allow me to have myPlot as a ggplot object which is why I've avoided any complicated grid stuff until this point.


Answer:

An approach that uses grid functions to edit your plot.

It is easy to draw a rectangle within a grid viewport. Is it possible to construct a grid viewport that will overlay the set of ggplot panels exactly? The answer is "Yes". The trick, for drawing the rectangle, is to get the x-axis "native" coordinates from the ggplot_build information into the grid viewport.

library(ggplot2)
library(grid)

plot <- ggplot(mpg, aes(displ, hwy)) + 
    geom_point() + 
    facet_grid(class ~ .) 

plot

# Construct a grid.layout that is the same as the ggplot layout
gt = ggplotGrob(plot)
lay = grid.layout(nrow = length(gt$heights), ncol = length(gt$widths),
                  widths = gt$widths, heights = gt$heights)

# Push to this viewport
pushViewport(viewport(layout = lay))

# Within the layout, push to a viewport that spans the plot panels.
pos = gt$layout[grep("panel", gt$layout$name), c("t", "l")]  # Positions of the panels
pushViewport(viewport(layout.pos.col = pos$l, layout.pos.row = min(pos$t):max(pos$t)))

# Get the limits of the ggplot's x-scale, including any expansion.
## For ggplot ver 2.2.1
# x.axis.limits = ggplot_build(plot)$layout$panel_ranges[[1]][["x.range"]]

## For ver 3.0.0
# axis.limits = ggplot_build(plot)$layout$panel_params[[1]]$x.range
# But possibly the following will be more immune to future changes to ggplot
x.axis.limits = summarise_layout(ggplot_build(plot))[1, c('xmin', 'xmax')]


# Set up a data viewport,
# so that the x-axis units are, in effect, "native", 
# but y-axis units are, in effect, "npc".
# And push to the data viewport.
pushViewport(dataViewport(yscale = c(0, 1), 
                          xscale = x.axis.limits))

# Draw the rectangle
grid.rect(x = 3, y = 0,
          width = 1, height = 1,
          just = c("left", "bottom"), default.units = "native",
          gp = gpar(fill = "green", col = "transparent", alpha = .2))

# Back to the root viewport
upViewport(0)

Question:

I'm using the ggforce package to generate facetted plots over several pages:

library(ggforce) 

for(i in 1:6){
  ggplot(diamonds) +
    geom_point(aes(carat, price), alpha = 0.1) +
    facet_wrap_paginate(~cut:clarity, ncol = 2, nrow = 2, page = i)

  ggsave(paste0("~/diamonds_", i, ".pdf"))
}

which is generating the expected 6 PDF files:

What is the easiest way have the output in one single pdf with 6 pages?

I understand this can be done with the reports and pdftools packages, but I'm wondering if there's a more direct way to accomplish this. I'd expect ggforce to provide the functionality for the output to be single-paged, but it looks like that's not the case?


Answer:

You don't even need to use ggsave you can put all these plots into one pdf by:

pdf("~/diamonds_all.pdf")
for(i in 1:6){
  print(ggplot(diamonds) +
          geom_point(aes(carat, price), alpha = 0.1) +
          facet_wrap_paginate(~cut:clarity, ncol = 2, nrow = 2, page = i))

}
dev.off()

Question:

How can I make a plot like this with two different-sized half circles (or other shapes such as triangles etc.)?

I've looked into a few options: Another post suggested using some unicode symbol, that didn't work for me. And if I use a vector image, how can I properly adjust the size parameter so the 2 circles touch each other?

Sample data (I would like to make the size of the two half-circles equal to circle1size and circle2size):

df = data.frame(circle1size = c(1, 3, 2),
                circle2size = c(3, 6, 5),
                middlepointposition = c(1, 2, 3))

And ultimately is there a way to position the half-circles at different y-values too, to encode a 3rd dimension, like so?

Any advice is much appreciated.


Answer:

What you're asking for is a bar plot in polar coordinates. This can be done easily in ggplot2. Note that we need to map y = sqrt(count) to get the area of the half circle proportional to the count.

df <- data.frame(x = c(1, 2),
                 type = c("Investors", "Assignees"),
                 count = c(19419, 1132))

ggplot(df, aes(x = x, y = sqrt(count), fill = type)) + geom_col(width = 1) +
  scale_x_discrete(expand = c(0,0), limits = c(0.5, 2.5)) +
  coord_polar(theta = "x", direction = -1)

Further styling would have to be applied to remove the gray background, remove the axes, change the color, etc., but that's all standard ggplot2.

Update 1: Improved version with multiple countries.

df <- data.frame(x = rep(c(1, 2), 3),
                 type = rep(c("Investors", "Assignees"), 3),
                 country = rep(c("Japan", "Germany", "Korea"), each = 2),
                 count = c(19419, 1132, 8138, 947, 8349, 436))

df$country <- factor(df$country, levels = c("Japan", "Germany", "Korea"))

ggplot(df, aes(x=x, y=sqrt(count), fill=type)) + geom_col(width =1) +
  scale_x_continuous(expand = c(0, 0), limits = c(0.5, 2.5)) +
  scale_y_continuous(expand = c(0, 0)) +
  coord_polar(theta = "x", direction = -1) +
  facet_wrap(~country) +
  theme_void()

Update 2: Drawing the individual plots at different locations.

We can do some trickery to take the individual plots and plot them at different locations in an enclosing plot. This works, and is a generic method that can be done with any sort of plot, but it's probably overkill here. Anyways, here is the solution.

library(tidyverse) # for map
library(cowplot) # for draw_text, draw_plot, get_legend, insert_yaxis_grob

# data frame of country data
df <- data.frame(x = rep(c(1, 2), 3),
                 type = rep(c("Investors", "Assignees"), 3),
                 country = rep(c("Japan", "Germany", "Korea"), each = 2),
                 count = c(19419, 1132, 8138, 947, 8349, 436))

# list of coordinates
coord_list = list(Japan = c(1, 3), Germany = c(2, 1), Korea = c(3, 2))

# make list of individual plots
split(df, df$country) %>% 
  map( ~ ggplot(., aes(x=x, y=sqrt(count), fill=type)) + geom_col(width =1) +
  scale_x_continuous(expand = c(0, 0), limits = c(0.5, 2.5)) +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 160)) +
  draw_text(.$country[1], 1, 160, vjust = 0) +
  coord_polar(theta = "x", start = 3*pi/2) +
  guides(fill = guide_legend(title = "Type", reverse = T)) +
  theme_void() + theme(legend.position = "none") ) -> plotlist

# extract the legend
legend <- get_legend(plotlist[[1]] + theme(legend.position = "right"))

# now plot the plots where we want them
width = 1.3
height = 1.3
p <- ggplot() + scale_x_continuous(limits = c(0.5, 3.5)) + scale_y_continuous(limits = c(0.5, 3.5))
for (country in names(coord_list)) {
  p <- p + draw_plot(plotlist[[country]], x = coord_list[[country]][1]-width/2,
                     y = coord_list[[country]][2]-height/2,
                     width = width, height = height)  
}
# plot without legend
p

# plot with legend
ggdraw(insert_yaxis_grob(p, legend))

Update 3: Completely different approach, using geom_arc_bar() from the ggforce package.

library(ggforce)
df <- data.frame(start = rep(c(-pi/2, pi/2), 3),
                 type = rep(c("Investors", "Assignees"), 3),
                 country = rep(c("Japan", "Germany", "Korea"), each = 2),
                 x = rep(c(1, 2, 3), each = 2),
                 y = rep(c(3, 1, 2), each = 2),
                 count = c(19419, 1132, 8138, 947, 8349, 436))

r <- 0.5
scale <- r/max(sqrt(df$count))

ggplot(df) + 
  geom_arc_bar(aes(x0 = x, y0 = y, r0 = 0, r = sqrt(count)*scale,
                   start = start, end = start + pi, fill = type),
               color = "white") +
  geom_text(data = df[c(1, 3, 5), ],
            aes(label = country, x = x, y = y + scale*sqrt(count) + .05),
            size =11/.pt, vjust = 0)+ 
  guides(fill = guide_legend(title = "Type", reverse = T)) +
  xlab("x axis") + ylab("y axis") +
  coord_fixed() +
  theme_bw()

Question:

I discovered few weeks ago ggforce, which has a great features to plot ellipse. But I don't manage to use it in log plots. Here is an example:

I would like to use the ellipse to circle this group

library(ggforce)
library(ggplot2)

ggplot(mtcars)+
  geom_point(aes(hp,disp))+
  geom_ellipse(aes(x0 = 230, y0 = 450, a = 80, b = 30, angle = -10))

But I would like to do this in a log plot. If I naively do

ggplot(mtcars)+
  geom_point(aes(hp,disp))+
  geom_ellipse(aes(x0 = 230, y0 = 450, a = 80, b = 30, angle = -10))+
  scale_y_log10()

I obtain a giant ellipse:

It looks like the ellipse parameters are not log transformed. I could try to reduce the parameter axis to get the good size on the log axis, something like:

ggplot(mtcars)+
  geom_point(aes(hp,disp))+
  scale_y_log10()+
  geom_ellipse(aes(x0 = 230, y0 = 450, a = 80, b = 0.05, angle =0))

which works:

But only if the angle is 0. If not, the two wxis are mixed and I can't get the ellipse I want:

ggplot(mtcars)+
  geom_point(aes(hp,disp))+
  scale_y_log10()+
  geom_ellipse(aes(x0 = 230, y0 = 450, a = 80, b = 0.05, angle = -10))

How can I plot an ellipse in a log or log-log plot in ggplot ? Is there any feasible workaround with ggforce ? Is there any other "simple" solution (other than coding the ellipse in semi-log coordinates) ?


Answer:

What actually works for me is to transform the coordinate system instead of the y scale.

ggplot(mtcars) +
  geom_point(aes(hp,disp)) +
  geom_ellipse(aes(x0 = 230, y0 = 450, a = 80, b = 30, angle = -10)) +
  coord_trans(y = "log10")

To be honest it intuitively makes sense to me to use the coord transformation - it resembles coord_map where you're also transforming the coordinates when plotting polygons in different shapes - but I don't know enough internals to explain why scale transformation does not work.

Question:

I am plotting a time series and I want to zoom on few observations. This can be done using facet_zoom() from ggforce package.

library(dplyr)
library(ggplot2)
library(ggforce)
library(stringr)


airquality %>% 
  mutate(month_day = seq(as.Date("2000/1/1"), 
                         by = "month", 
                         length.out = n())) %>% 
  ggplot(aes(x = month_day, y = Temp)) + 
  geom_line() +
  facet_zoom(x = month_day > "2010/1/1" & month_day < "2010/9/1")

Resulting plot:

However, I would like to manipulate the scale on y-axis of the lower panel plot, making it smaller. Is there a way to do this?


Answer:

Use xy instead of x, and set horizontal to TRUE to auto fit the y-axis:

airquality %>% 
  mutate(month_day = seq(as.Date("2000/1/1"), 
                         by = "month", 
                         length.out = n())) %>% 
  ggplot(aes(x = month_day, y = Temp)) + 
  geom_line() +
  facet_zoom(xy = month_day > "2010/1/1" & month_day < "2010/9/1", horizontal = FALSE)

Question:

With facet_zoom() from the ggforce package one can create nice zooms to highlight certain regions of a plot. Unfortunately, when zooming in on the y axis the original plot is always on the right side.

Is there a way to place the original plot on the left?

This would feel more intuitive to first look at the main plot and then at the zoomed region. As an example I would like to swap the position of the two facets in this plot:

(No reproducible example added, since I believe this is a question about the existence of a certain functionality.)


Answer:

I've tweaked the current code for FacetZoom on GitHub to swop the horizontal order from [zoom, original] to [original, zoom]. The changes aren't complicated, but they are scattered throughout draw_panels() function's code, so the full code is rather long.

Result:

# example 1, with split = FALSE, horizontal = TRUE (i.e. default settings)
p1 <- ggplot(mtcars, aes(x = mpg, y = disp, colour = factor(cyl))) +
  geom_point() +
  theme_bw()    
p1 + ggtitle("Original") + facet_zoom(y = disp > 300)
p1 + ggtitle("Modified") + facet_zoom2(y = disp > 300)

# example 2, with split = TRUE
p2 <- ggplot(iris, aes(Petal.Length, Petal.Width, colour = Species)) +
  geom_point() +
  theme_bw()    
p2 + ggtitle("Original") + 
  facet_zoom(xy = Species == "versicolor", split = TRUE)
p2 + ggtitle("Modified") + 
  facet_zoom2(xy = Species == "versicolor", split = TRUE)

Code used (I've commented out the original code, where modified code is used, & indicated the packages for functions from other packages):

library(ggplot)
library(ggforce)
library(grid)

# define facet_zoom2 function to use FacetZoom2 instead of FacetZoom
# (everything else is the same as facet_zoom)
facet_zoom2 <- function(x, y, xy, zoom.data, xlim = NULL, ylim = NULL, 
                        split = FALSE, horizontal = TRUE, zoom.size = 2, 
                        show.area = TRUE, shrink = TRUE) {
  x <- if (missing(x)) if (missing(xy)) NULL else lazyeval::lazy(xy) else lazyeval::lazy(x)
  y <- if (missing(y)) if (missing(xy)) NULL else lazyeval::lazy(xy) else lazyeval::lazy(y)
  zoom.data <- if (missing(zoom.data)) NULL else lazyeval::lazy(zoom.data)
  if (is.null(x) && is.null(y) && is.null(xlim) && is.null(ylim)) {
    stop("Either x- or y-zoom must be given", call. = FALSE)
  }
  if (!is.null(xlim)) x <- NULL
  if (!is.null(ylim)) y <- NULL
  ggproto(NULL, FacetZoom2,
          shrink = shrink,
          params = list(
            x = x, y = y, xlim = xlim, ylim = ylim, split = split, zoom.data = zoom.data,
            zoom.size = zoom.size, show.area = show.area,
            horizontal = horizontal
          )
  )
}

# define FacetZoom as a ggproto object that inherits from FacetZoom,
# with a modified draw_panels function. the compute_layout function references
# the version currently on GH, which is slightly different from the CRAN
# package version.
FacetZoom2 <- ggproto(
  "FacetZoom2",
  ggforce::FacetZoom,

  compute_layout = function(data, params) {
    layout <- rbind( # has both x & y dimension
      data.frame(name = 'orig', SCALE_X = 1L, SCALE_Y = 1L),
      data.frame(name = 'x', SCALE_X = 2L, SCALE_Y = 1L),
      data.frame(name = 'y', SCALE_X = 1L, SCALE_Y = 2L),
      data.frame(name = 'full', SCALE_X = 2L, SCALE_Y = 2L),
      data.frame(name = 'orig_true', SCALE_X = 1L, SCALE_Y = 1L),
      data.frame(name = 'zoom_true', SCALE_X = 1L, SCALE_Y = 1L)
    )
    if (is.null(params$y) && is.null(params$ylim)) { # no y dimension
      layout <- layout[c(1,2, 5:6),]
    } else if (is.null(params$x) && is.null(params$xlim)) { # no x dimension
      layout <- layout[c(1,3, 5:6),]
    }
    layout$PANEL <- seq_len(nrow(layout))
    layout
  },

  draw_panels = function(panels, layout, x_scales, y_scales, ranges, coord,
                         data, theme, params) {

    if (is.null(params$x) && is.null(params$xlim)) {
      params$horizontal <- TRUE
    } else if (is.null(params$y) && is.null(params$ylim)) {
      params$horizontal <- FALSE
    }
    if (is.null(theme[['zoom']])) {
      theme$zoom <- theme$strip.background
    }
    if (is.null(theme$zoom.x)) {
      theme$zoom.x <- theme$zoom
    }
    if (is.null(theme$zoom.y)) {
      theme$zoom.y <- theme$zoom
    }
    axes <- render_axes(ranges, ranges, coord, theme, FALSE)
    panelGrobs <- ggforce:::create_panels(panels, axes$x, axes$y)
    panelGrobs <- panelGrobs[seq_len(length(panelGrobs) - 2)]
    if ('full' %in% layout$name && !params$split) {
      panelGrobs <- panelGrobs[c(1, 4)]
    }

    # changed coordinates in indicator / lines to zoom from 
    # the opposite horizontal direction
    if ('y' %in% layout$name) {
      if (!inherits(theme$zoom.y, 'element_blank')) {
        zoom_prop <- scales::rescale(
          y_scales[[2]]$dimension(ggforce:::expansion(y_scales[[2]])),
          from = y_scales[[1]]$dimension(ggforce:::expansion(y_scales[[1]])))
        indicator <- polygonGrob(
          x = c(0, 0, 1, 1), # was x = c(1, 1, 0, 0), 
          y = c(zoom_prop, 1, 0), 
          gp = gpar(col = NA, fill = alpha(theme$zoom.y$fill, 0.5)))
        lines <- segmentsGrob(
          x0 = c(1, 1), x1 = c(0, 0), # was x0 = c(0, 0), x1 = c(1, 1)
          y0 = c(0, 1), y1 = zoom_prop,
          gp = gpar(col = theme$zoom.y$colour,
                    lty = theme$zoom.y$linetype,
                    lwd = theme$zoom.y$size,
                    lineend = 'round'))
        indicator_h <- grobTree(indicator, lines)
      } else {
        indicator_h <- zeroGrob()
      }
    }

    if ('x' %in% layout$name) {
      if (!inherits(theme$zoom.x, 'element_blank')) {
        zoom_prop <- scales::rescale(x_scales[[2]]$dimension(ggforce:::expansion(x_scales[[2]])),
                                     from = x_scales[[1]]$dimension(ggforce:::expansion(x_scales[[1]])))
        indicator <- polygonGrob(c(zoom_prop, 1, 0), c(1, 1, 0, 0), 
                                 gp = gpar(col = NA, fill = alpha(theme$zoom.x$fill, 0.5)))
        lines <- segmentsGrob(x0 = c(0, 1), y0 = c(0, 0), x1 = zoom_prop, y1 = c(1, 1), 
                              gp = gpar(col = theme$zoom.x$colour,
                                        lty = theme$zoom.x$linetype,
                                        lwd = theme$zoom.x$size,
                                        lineend = 'round'))
        indicator_v <- grobTree(indicator, lines)
      } else {
        indicator_v <- zeroGrob()
      }
    }

    if ('full' %in% layout$name && params$split) {
      space.x <- theme$panel.spacing.x
      if (is.null(space.x)) space.x <- theme$panel.spacing
      space.x <- unit(5 * as.numeric(convertUnit(space.x, 'cm')), 'cm')
      space.y <- theme$panel.spacing.y
      if (is.null(space.y)) space.y <- theme$panel.spacing
      space.y <- unit(5 * as.numeric(convertUnit(space.y, 'cm')), 'cm')

      # change horizontal order of panels from [zoom, original] to [original, zoom]
      # final <- gtable::gtable_add_cols(panelGrobs[[3]], space.x)
      # final <- cbind(final, panelGrobs[[1]], size = 'first')
      # final_tmp <- gtable::gtable_add_cols(panelGrobs[[4]], space.x)
      # final_tmp <- cbind(final_tmp, panelGrobs[[2]], size = 'first')
      final <- gtable::gtable_add_cols(panelGrobs[[1]], space.x)
      final <- cbind(final, panelGrobs[[3]], size = 'first')
      final_tmp <- gtable::gtable_add_cols(panelGrobs[[2]], space.x)
      final_tmp <- cbind(final_tmp, panelGrobs[[4]], size = 'first')

      final <- gtable::gtable_add_rows(final, space.y)
      final <- rbind(final, final_tmp, size = 'first')
      final <- gtable::gtable_add_grob(final, list(indicator_h, indicator_h),
                                       c(2, 6), 3, c(2, 6), 5,
                                       z = -Inf, name = "zoom-indicator")
      final <- gtable::gtable_add_grob(final, list(indicator_v, indicator_v), 
                                       3, c(2, 6), 5, 
                                       z = -Inf, name = "zoom-indicator")
      heights <- unit.c(
        unit(max_height(list(axes$x[[1]]$top, axes$x[[3]]$top)), 'cm'),
        unit(1, 'null'),
        unit(max_height(list(axes$x[[1]]$bottom, axes$x[[3]]$bottom)), 'cm'),
        space.y,
        unit(max_height(list(axes$x[[2]]$top, axes$x[[4]]$top)), 'cm'),
        unit(params$zoom.size, 'null'),
        unit(max_height(list(axes$x[[2]]$bottom, axes$x[[4]]$bottom)), 'cm')
      )

      # swop panel width specifications according to the new horizontal order
      widths <- unit.c(
        # unit(max_width(list(axes$y[[3]]$left, axes$y[[4]]$left)), 'cm'),
        # unit(params$zoom.size, 'null'),
        # unit(max_height(list(axes$y[[3]]$right, axes$y[[4]]$right)), 'cm'),
        # space.x,
        # unit(max_width(list(axes$y[[1]]$left, axes$y[[2]]$left)), 'cm'),
        # unit(1, 'null'),
        # unit(max_height(list(axes$y[[1]]$right, axes$y[[2]]$right)), 'cm')        
        unit(max_width(list(axes$y[[1]]$left, axes$y[[2]]$left)), 'cm'),
        unit(1, 'null'),
        unit(max_height(list(axes$y[[1]]$right, axes$y[[2]]$right)), 'cm'),
        space.x,
        unit(max_width(list(axes$y[[3]]$left, axes$y[[4]]$left)), 'cm'),
        unit(params$zoom.size, 'null'),
        unit(max_height(list(axes$y[[3]]$right, axes$y[[4]]$right)), 'cm')

      )
      final$heights <- heights
      final$widths <- widths
    } else {
      if (params$horizontal) {
        space <- theme$panel.spacing.x
        if (is.null(space)) space <- theme$panel.spacing
        space <- unit(5 * as.numeric(convertUnit(space, 'cm')), 'cm')
        heights <- unit.c(
          unit(max_height(list(axes$x[[1]]$top, axes$x[[2]]$top)), 'cm'),
          unit(1, 'null'),
          unit(max_height(list(axes$x[[1]]$bottom, axes$x[[2]]$bottom)), 'cm')
        )

        # change horizontal order of panels from [zoom, original] to [original, zoom]
        # first <- gtable::gtable_add_cols(panelGrobs[[2]], space)
        # first <- cbind(final, panelGrobs[[1]], size = 'first')
        final <- gtable::gtable_add_cols(panelGrobs[[1]], space) 
        final <- cbind(final, panelGrobs[[2]], size = "first") 

        final$heights <- heights

        # swop panel width specifications according to the new horizontal order
        # unit(c(params$zoom.size, 1), 'null')
        final$widths[panel_cols(final)$l] <- unit(c(1, params$zoom.size), 'null') 

        final <- gtable::gtable_add_grob(final, indicator_h, 2, 3, 2, 5, 
                                         z = -Inf, name = "zoom-indicator")
      } else {
        space <- theme$panel.spacing.y
        if (is.null(space)) space <- theme$panel.spacing
        space <- unit(5 * as.numeric(convertUnit(space, 'cm')), 'cm')
        widths <- unit.c(
          unit(max_width(list(axes$y[[1]]$left, axes$y[[2]]$left)), 'cm'),
          unit(1, 'null'),
          unit(max_height(list(axes$y[[1]]$right, axes$y[[2]]$right)), 'cm')
        )
        final <- gtable::gtable_add_rows(panelGrobs[[1]], space)
        final <- rbind(final, panelGrobs[[2]], size = 'first')
        final$widths <- widths
        final$heights[panel_rows(final)$t] <- unit(c(1, params$zoom.size), 'null')
        final <- gtable::gtable_add_grob(final, indicator_v, 3, 2, 5, 
                                         z = -Inf, name = "zoom-indicator")
      }
    }
    final
  }
)

Note: create_panels and expansion are un-exported functions from the ggforce package, so I referenced them with triple colons. This isn't robust for writing packages, but should suffice as a temporary workaround.

Update 30 Oct 2019: A suggestion for those seeing errors like Invalid 'type' (list) of argument after trying to use this solution as-is. The issue is likely due to updates made to the ggforcepackage since this solution was developed. To get the code in this solution working again, install the version of ggforce that was available when the solution was developed. This can be done with the devtools package pointing to the 4008a2e commit:

devtools::install_github("thomasp85/ggforce", ref = '4008a2e')

Question:

I would like to label points in a scatterplot, but only those within the facet_zoom panel. Here is an example:

library(ggplot2)
library(ggforce)
library(ggrepel)
library(magrittr)

labels <- letters
example_values_x <- rnorm(26)
example_values_y <- rnorm(26)

df <- data.frame(labels, 
                 example_values_x, 
                 example_values_y)
df %>% ggplot(aes(y = example_values_y, 
                  x = example_values_x)) +
  geom_point() +
  facet_zoom(x = example_values_x > 0.5) + 
  geom_label_repel(data = filter(df, example_values_x > 0.5), aes(label = labels))

Any idea how to make it so the labels don't also appear on the non-zoomed panel?


Answer:

NOTE: The following answer works with the GitHub version of ggforce. As of writing this, the version that's on CRAN appears to have a different interface for facet_zoom(), even though the package version is the same.

First, take your subset of data being labeled and add a zoom column, specifying whether the data should be rendered in the zoomed panel (TRUE), the original panel (FALSE), or both (NA):

dftxt <- dplyr::filter(df, example_values_x > 0.5) %>%
  dplyr::mutate( zoom = TRUE )      ## All entries to appear in the zoom panel only

You can now pass this new data frame to geom_label_repel, while telling facet_zoom() to use the zoom column to determine where the data should be drawn:

df %>% ggplot(aes(y = example_values_y, 
                  x = example_values_x)) +
  geom_point() +
  facet_zoom(x = example_values_x > 0.5, zoom.data=zoom) +   # Note the zoom.data argument
  geom_label_repel(data = dftxt, aes(label = labels))

Note that because the original df doesn't have a zoom column, facet_zoom() will treat it as NA and draw geom_point() in both panels, as desired:

Question:

Using this

DATA

df <- read.table(text = c("
Site    measured    simulated
site1   9.1 6.8
                          site2 163.1   128.1
                          site3 126 75.9
                          site4 741.2   843
                          site5 2215.1  1937.7
                          site6 283.6   423.4
                          site7 115.6   92.5
                          site8 12.1    15.3
                          site9 13.4    15.8
                          site10    475.7   296.1
                          site11    1   1.4
                          site12    84.5    131.9
                          site13    74.1    43.9
                          site14    19.2    33.3
                          site15    74.8    41.1
                          site16    287.8   366.9"), header =T)

and the script below

library(tidyr) 
library(dplyr)
library(ggplot2)
library(ggforce)
df_reorder <-  transform(df, 
                         Site_reorder = reorder(Site, -measured))
df_long <- df_reorder %>% gather("id", "value", 2:3) 


ggplot(df_long, aes(x = Site_reorder, y = value, fill = id))+
  geom_bar(stat = "identity", position = "dodge", width = 0.7)+
  scale_fill_manual(values=c("red", "black")) + 
  theme_bw()+
  facet_zoom(x = Site_reorder%in%c("site14", "site9", "site8", "site1", "site11"))+
  labs(x = "", y = " ")+
  theme(legend.position = c(0.90, 0.90), legend.background = element_rect(fill="transparent"),
        legend.title = element_blank(), axis.text.x=element_text(angle=55, vjust=1,  hjust=1,size = 8))

I wanted the zoom to be for the last five sites (site14, site9, site8, site1 and site11). Instead, it zoomed into the first five sites (site5, site4, site10, site16, and site6)

Any suggestions how to make the zoom for the last five sites will be appreciated?


Answer:

ggforce only zoom on continous scale.

1- You can use as.numeric to convert levels to numeric,

2- then use scale_x_continous to print the sites names

ggplot(df_long, aes(x = as.numeric(Site_reorder), y = value, fill = id))+
  geom_bar(stat = "identity", position = "dodge", width = 0.7)+
  scale_fill_manual(values=c("red", "black")) + 
  theme_bw()+
  facet_zoom(xy = Site_reorder%in%c("site14", "site9", "site8", "site1", "site11"), horizontal=FALSE) + 
  scale_x_continuous(
    breaks = 1:length(levels(df_long$Site_reorder)),
    label = levels(df_long$Site_reorder)
  )+
  labs(x = "", y = " ")+
  theme(legend.position = c(0.90, 0.90), legend.background = element_rect(fill="transparent"),
        legend.title = element_blank(), axis.text.x=element_text(angle=55, vjust=1,  hjust=1,size = 8))

Question:

Context

This question is part of a project to plot genetic maps in R. The common representation is a "scale" of horizontal trait bordered by two half circle. genetic map example genetic maps are roughly a sequence of position.

Goal

I'm trying to make a half circle matching the "ladders of the scale" and with adjustable high so that it always look like a circle and not like some sort of oval shape.

Actual code
library(ggforce)  
df <- data.frame(position=rnorm(n=15)*10)  
ggplot(aes(xmin=-2.5,ymin=position-0.1,xmax=2.5,ymax=position+0.1),data=df)+  
geom_rect() +  
geom_arc_bar(aes(x0 = 0, y0 = max(position), r0 = 0, r = 2.5, start =-pi/2,end = pi/2), color = "grey20")+  
geom_arc_bar(aes(x0 = 0, y0 = min(position), r0 = 0, r = 2.5, start = pi/2,end = 3*pi/2), color = "grey20")  

Question

So my question is: How to make a reactive shape scaling with the size of Rplot viewing window so that the shape always look like a circle and stay link to my upper "ladder"?

Try

I can adjust manually the size of the plot so that x.axis and y.axis have the same scale before exporting the graph but this is not quite efficient and will become really difficult if I have multiple chromosome on the same plot. I would be glad to answer comments if needed and hope my question is clear enough!


Answer:

coord_fixed will make sure that the x and y are always scaled 1:1, even when rescaling the plot.

ggplot(aes(xmin=-2.5,ymin=position-0.1,xmax=2.5,ymax=position+0.1),data=df)+  
  geom_rect() +  
  geom_arc_bar(aes(x0 = 0, y0 = max(position), r0 = 0, r = 2.5, start =-pi/2,end = pi/2), color = "grey20")+  
  geom_arc_bar(aes(x0 = 0, y0 = min(position), r0 = 0, r = 2.5, start = pi/2,end = 3*pi/2), color = "grey20") +
  coord_fixed()

Question:

I have a data frame containing the following data:

> data_graph
# A tibble: 12 x 4
# Groups:   ATTPRO, ATTMAR [?]
       x     y group    nb
   <dbl> <dbl> <chr> <int>
 1     0     0     1  1060
 2     0     0     2   361
 3     0     0     3   267
 4     0     1     1   788
 5     0     1     2   215
 6     0     1     3    80
 7     1     0     1   485
 8     1     0     2   168
 9     1     0     3   101
10     1     1     1  6306
11     1     1     2  1501
12     1     1     3   379

My objective is to have the following chart:

  • Both x and y, qualitative variables, to be put as X/Y axis
  • nb, quantitative variable, representing pie size
  • group, qualitative variable, representing pie parts

The best result approching this using ggplot2 package is only giving me bubbles, with this code. I can't find a solution to put pies within it:

library(ggplot2)
  ggplot(data_graph, aes(y = factor(y),x = factor(x))) +
  geom_point(aes(colour = group, size = nb)) +
  theme_bw() + 
  cale_size(range = c(1, 20)) +
  labs(x = "x", y = "y", color = "group", size = "nb")

Using scatterpie package did not help that much. This time pies are well drawn, but I can't find a way to use nb to define pie size. Also, x and y are treated as quantitative variables (I tried factor() without any chance) instead of qualitative ones. The result is pretty ugly, without a full legend.

> tmp
  x y    A    B   C
1 0 0 1060  361 267
2 0 1  788  215  80
3 1 0  485  168 101
4 1 1 6306 1501 379

library(scatterpie)
ggplot() +
   geom_scatterpie(aes(x = x, y = y), data = tmp, cols = c("A", "B", "C")) +
   coord_fixed()

How can this code be altered in order to have the 1st chart with the 2nd one's pies?


Answer:

This seems to be a case for geom_arc_bar() from ggforce, with some dplyr magic. This treats x and y as continuous variables, but that's not a problem, you can pretend they are discrete by setting the right axis settings.

The data:

data_graph <- read.table(text = "x     y group    nb
1     0     0     1  1060
2     0     0     2   361
3     0     0     3   267
4     0     1     1   788
5     0     1     2   215
6     0     1     3    80
7     1     0     1   485
8     1     0     2   168
9     1     0     3   101
10     1     1     1  6306
11     1     1     2  1501
12     1     1     3   379", header = TRUE)

The code:

library(ggforce)
library(dplyr)

# make group a factor
data_graph$group <- factor(data_graph$group)

# add case variable that separates the four pies
data_graph <- cbind(data_graph, case = rep(c("Aaaa", "Bbbb", "Cccc", "Dddd"), each = 3))

# calculate the start and end angles for each pie
data_graph <- left_join(data_graph,
  data_graph %>% 
    group_by(case) %>%
    summarize(nb_total = sum(nb))) %>%
  group_by(case) %>%
  mutate(nb_frac = 2*pi*cumsum(nb)/nb_total,
         start = lag(nb_frac, default = 0))

# position of the labels
data_labels <- data_graph %>% 
  group_by(case) %>%
  summarize(x = x[1], y = y[1], nb_total = nb_total[1])

# overall scaling for pie size
scale = .5/sqrt(max(data_graph$nb_total))

# draw the pies
ggplot(data_graph) + 
  geom_arc_bar(aes(x0 = x, y0 = y, r0 = 0, r = sqrt(nb_total)*scale,
                   start = start, end = nb_frac, fill = group)) +
  geom_text(data = data_labels,
            aes(label = case, x = x, y = y + scale*sqrt(nb_total) + .05),
            size =11/.pt, vjust = 0) +
  coord_fixed() +
  scale_x_continuous(breaks = c(0, 1), labels = c("X0", "X1"), name = "x axis") +
  scale_y_continuous(breaks = c(0, 1), labels = c("Y0", "Y1"), name = "y axis") +
  theme_minimal() +
  theme(panel.grid.minor = element_blank())

Question:

Probably a simple one.

Is there a way to eliminate the bordering line in a ggforce::geom_arc_bar pie plot?

pie <- data.frame(
    state = c('eaten', 'eaten but said you didn\'t', 'cat took it', 
              'for tonight', 'will decompose slowly'),
    focus = c(0.2, 0, 0, 0, 0),
    start = c(0, 1, 2, 3, 4),
    end = c(1, 2, 3, 4, 2*pi),
    amount = c(4,3, 1, 1.5, 6),
    stringsAsFactors = FALSE
)
ggplot(pie) + 
    geom_arc_bar(aes(x0 = 0, y0 = 0, r0 = 0, r = 1, amount = amount, 
                     fill = state, explode = focus), stat = 'pie') + 
    scale_fill_brewer('', palette = 'Set1') +
    coord_fixed()

For example, in the plot above either eliminate the black border line or make its color the same as the fill color?

And another question, is it possible to eliminate the border line only from the perimeter of the pie? So only the demarkation of the slices stays?


Answer:

ggplot uses two parameters in general for colours:

col = 

and

fill = 

You assigned fill (internal), but didnt deal with col (outline).

the following should work, where I assign col=NA

ggplot(pie) + 
  geom_arc_bar(aes(x0 = 0, y0 = 0, r0 = 0, r = 1, amount = amount, 
                   fill = state, explode = focus), col=NA,stat = 'pie') + 
  scale_fill_brewer('', palette = 'Set1') +
  coord_fixed()

EDIT: So to answer the second quesiton - I really don't know if this is possible (I am not overly familiar with ggforce) In light of this, I ask forgiveness in advance of this very 'hacky' solution to your problem. But I felt it was better to offer something for others to improve on.

To explain: keep hte original outline, but then plot a new outline the same colour as each segment using the geom_arc0 function"

geom_arc0(aes(x0 = 0, y0 = 0, r = 1, start = start, end = end, 
              colour = factor(state)), data = pie, ncp = 50)

in context:

ggplot(pie) + 
  geom_arc_bar(aes(x0 = 0, y0 = 0, r0 = 0, r = 1, amount = amount, 
                   fill = state, explode = focus), col="black",stat = 'pie') + 
  geom_arc0(aes(x0 = 0, y0 = 0, r = 1, start = start, end = end, 
                colour = factor(state)), data = pie, ncp = 50)
  scale_fill_brewer('', palette = 'Set1') +
  coord_fixed()

It may look a bit dodgy - if you increase the ncp parameter it adds more points to the outline (thus getting closer to a perfect circle) - i was limited to ncp=2000.

Again apologies that it is so hacky.

Question:

Is it currently possible to make the labels generated with ggforce::geom_mark_ellipse(label=) colored?

In the example below, I'd like to see versicolor typeface in green:


Answer:

Yes, but not in a straightforward way like you would with any other mapped aesthetics. You would have to build a new layer where you change the colour and filter the layers correctly. You can see this becoming quite laborious if you have 10+ groups each needing their own label colour.

library(ggplot2)
library(ggforce)

ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_ellipse(aes(fill = Species, label = Species,
                        filter = Species != 'versicolor')) +
  geom_mark_ellipse(aes(fill = Species, label = Species,
                        filter = Species == 'versicolor'),
                    label.colour = "green") +
  geom_point()

Question:

I am trying to plot multiple piecharts on top of an image. I want to use custom_annotation to plot the rastered image. But right now I can't even get the multiple piecharts.

Ultimately I want 6 pies plotting in differnet spots over top of an image. imX and imY give the coordinates of where the pies should be on the image.

head(wholebody_cutLH_wide_t[c(1,2,102,103,104)])
Acidobacteriaceae Actinomycetaceae  imX imY radius
1      0.000000e+00     7.665687e-05 2.00 5.5    0.5
2      0.000000e+00     4.580237e-04 1.50 1.0    0.5
3      0.000000e+00     4.112573e-04 1.75 2.0    0.5
4      6.431473e-04     3.856008e-02 0.30 1.0    0.5
5      0.000000e+00     3.013013e-04 1.50 4.8    0.5
6      3.399756e-05     1.372986e-02 1.50 5.2    0.5

Now here is my attempt with scatterpie:

ggplot(wholebody_cutLH_wide_t) +
 #  annotation_custom(g, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) +
 geom_scatterpie(aes(x=imX, y=imY,r=radius),
            data=wholebody_cutLH_wide_t, cols=NA,color=sample(allcolors,101)) +
 scale_color_manual(values=sample(allcolors,101)) +
 scale_x_continuous(expand=c(0,0), lim=c(0,3)) +
 scale_y_continuous(expand=c(0,0), lim=c(0,6)) +
 theme(legend.position="none",
    panel.background = element_rect(fill = "transparent") # bg of the panel
    , plot.background = element_rect(fill = "transparent") # bg of the plot
    , panel.grid.major = element_blank() # get rid of major grid
    , panel.grid.minor = element_blank(), # get rid of minor grid
    line = element_blank(),
    text = element_blank(),
    title = element_blank()
  )  

Now my error is:

Error: Only strings can be converted to symbols

Here is my attempt with dplyr and ggforce:

dat_pies<-left_join(wholebody_cutLH,
                wholebody_cutLH %>%
                  group_by(tax_rank) %>%
                  summarize(Cnt_total = sum(count_norm))) %>%
group_by(tax_rank) %>%
mutate(end_angle = 2*pi*cumsum(count_norm)/Cnt_total,      # ending angle for   each pie slice
     start_angle = lag(end_angle, default = 0),   # starting angle for each pie slice
     mid_angle = 0.5*(start_angle + end_angle))

ggplot(dat_pies) + 
geom_arc_bar(aes(x0 = imX, y0 = imY, r0 = 0, r = rpie,
               start = start_angle, end = end_angle, fill = Volume)) +
geom_text(aes(x = rlabel*sin(mid_angle), y = rlabel*cos(mid_angle), label = Cnt),
        hjust = 0.5, vjust = 0.5) +
coord_fixed() +
scale_x_continuous(expand=c(0,0), lim=c(0,3)) +
scale_y_continuous(expand=c(0,0), lim=c(0,6)) +

Here my error is :

Error in ggplot(dat_pies) + geom_arc_bar(aes(x0 = imX, y0 = imY, r0 = 0,  : 
could not find function "+<-"

Any help with either of these methods would be great. thanks


Answer:

In your first attempt you should use:

ggplot(wholebody_cutLH_wide_t) +
  geom_scatterpie(aes(x=imX, y=imY,r=radius),
     data=wholebody_cutLH_wide_t,
     cols=colnames(wholebody_cutLH_wide_t)[1:2],
     color=NA, alpha=.8)

Question:

I want to draw three-quarter-circles in ggplot2 with arrows and labels. Using ggplot2 and ggforce I found something that works, but it seems utterly complicated.

Are there simpler options to achieve what I want?


df <- data.frame(x = 100, y = 100, label = "R")

library(ggplot2)
library(ggforce)

r <- 10

ggplot(df) + 
  geom_circle(aes(x0 = x, y0 = y, r = r)) +
  geom_rect(aes(xmin = x, ymin = y, xmax = x + r + 1, ymax = x + r + 1), 
            fill = "white") +
  geom_segment(aes(x = x + r, y = y, xend = x + r, yend = y + 1), 
               arrow = arrow()) +
  annotate("text", x = df$x, y = df$y, label = df$label) + 
  theme_void()


Answer:

Would this be considered simpler?

ggplot(df) + 
  geom_arc(aes(x0 = x, y0 = y, r = r,
               start = 0, end = -1.5 * pi),
           arrow = arrow()) + 
  annotate("text", x = df$x, y = df$y, label = df$label) + 
  theme_void()

# equivalent to above with geom_text instead; I'm not sure if your actual use
# case has preference for one or the other
ggplot(df) + 
  geom_arc(aes(x0 = x, y0 = y, r = r,
               start = 0, end = -1.5 * pi),
           arrow = arrow()) + 
  geom_text(aes(x = x, y = y, label = label)) + 
  theme_void()

Question:

I would like to draw a sankey plot based on the below generated data structure where the edges between nodes correspond to an N3 column and their presence and thickness depend on the Value column. For the below dummy data, the plot would look like this (but with edge thickness corresponding to the value in the Value column). I haven't seen any example of sankey plots built like this. I've tried different options using the riverplot package, but as it doesn't seem to be able to handle the N3 column, it removes all duplicates of, e.g., edges between A and C.

set.seed(123)    

mat <- matrix(rbinom(20,100,0.01),4,5,dimnames=list(LETTERS[1:4],letters[1:5]))
mat

#   a b c d e
# A 0 3 1 1 0
# B 2 0 1 1 0
# C 1 1 3 0 0
# D 2 2 1 2 3

rowKey <- c("A"="N1","B"="N1","C"="N2","D"="N2")

edges = expand.grid(c(split(names(rowKey), rowKey), list(N3 = colnames(mat))))

edges2 = sapply(1:nrow(edges), function(i)
mat[row.names(mat) == edges$N1[i] | row.names(mat) == edges$N2[i],
    colnames(mat) == edges$N3[i]])

edges$Value = colSums(edges2) * (colSums(edges2 > 0) == nrow(edges2))
edges

#   N1 N2 N3 Value
#1   A  C  a     0
#2   B  C  a     3
#3   A  D  a     0
#4   B  D  a     4
#5   A  C  b     4
#6   B  C  b     0
#7   A  D  b     5
#8   B  D  b     0
#9   A  C  c     4
#10  B  C  c     4
#11  A  D  c     2
#12  B  D  c     2
#13  A  C  d     0
#14  B  C  d     0
#15  A  D  d     3
#16  B  D  d     3
#17  A  C  e     0
#18  B  C  e     0
#19  A  D  e     0
#20  B  D  e     0


# Plotting a sankey plot using the riverplot package
require(riverplot)
require(RColorBrewer)

nodes = data.frame(ID = unique(c(as.character(edges$N1),      
as.character(edges$N2))), stringsAsFactors = FALSE)
nodes$x <- c(rep(1,2),rep(2,2))
nodes$y <- c(0:1,0:1)

palette = paste0(brewer.pal(3, "Set1"), "60")
styles = lapply(nodes$y, function(n) {
  list(col = palette[n+1], lty = 0, textcol = "black")
})
names(styles) = nodes$ID

rp <- list(nodes=nodes, edges=edges[,-3], styles=styles)
class(rp) <- c(class(rp), "riverplot")
plot(rp, plot_area = 0.95, yscale=0.06, srt=0)

# Warning message:
# In checkedges(x2$edges, names(x2)) :
# duplicated edge information, removing 16 edges 

Answer:

Here's a solution using the geom_parallel_sets() from the ggforce package

devtools::install_github('thomasp85/ggforce')

edges1 <- gather_set_data(edges, 1:2)

ggplot(edges1, aes(x, id = id, split = y, value = Value)) +
      geom_parallel_sets(aes(fill = N3), alpha = 0.3, axis.width = 0.1) +
      geom_parallel_sets_axes(axis.width = 0.1) +
      geom_parallel_sets_labels(colour = 'white')

Question:

I've got a data like below:

structure(list(bucket = structure(1:23, .Label = c("(1.23,6.1]", 
"(6.1,10.9]", "(10.9,15.6]", "(15.6,20.4]", "(20.4,25.1]", "(25.1,29.9]", 
"(29.9,34.6]", "(34.6,39.4]", "(39.4,44.2]", "(44.2,48.9]", "(48.9,53.7]", 
"(53.7,58.4]", "(58.4,63.2]", "(63.2,68]", "(68,72.7]", "(72.7,77.5]", 
"(77.5,82.2]", "(82.2,87]", "(87,91.7]", "(91.7,96.5]", "(96.5,101]", 
"(101,106]", "(106,111]"), class = "factor"), value = c(0.996156321090158, 0.968144290236367, 0.882793110384066, 0.719390676388129, 0.497759597498133, 
0.311721580067415, 0.181244079443301, 0.0988516758834657, 0.0527504526341006, 
0.0278716018561911, 0.0145107725175315, 0.00785033086321829, 
0.00405759957072942, 0.00213190168252939, 0.00109610249274952, 
0.000578154695264754, 0.000301095727545301, 0.000155696457494707, 
8.2897211122996e-05, 4.09225082176349e-05, 2.33782236798641e-05, 
1.21665352966827e-05, 6.87373003802479e-06), bucket_id = 1:23), class = c("tbl_df", 
"tbl", "data.frame"), row.names = c(NA, -23L))

Which I want to visualise as a circular stacked bar plot:

cutoff_values <- seq(0, 115, by = 5)


library(tidyverse)
ex %>% 
  mutate(r0 = cutoff_values[-length(cutoff_values)],
         r = cutoff_values[-1]) %>% 
  mutate(x0 = 100, 
         y0 = 50) %>% 
  ggplot(aes(x0 = x0, y0 = y0, r0 = r0, r = r)) +
  ggforce::geom_arc_bar(aes(start = 0, end = 2 * pi, fill = value),
                        colour = NA) +
  theme_void() +
  labs(fill = 'colour')

But I also need to be able to mark out some particular bucket with different filling at best. So I need to be able to preserve filling using value with continuous scale, but also fill one particular stratum (let's say bucket == 15) with another colour, leaving the other strata (buckets) as they are. Is it possible? What are the alternatives to mark out bucket 15th?


Answer:

I believe that this can be done with the relayer package, which is still highly experimental. You can copy a subset of your data in a seperate geom and give it another fill aesthetic. This seperate geom can then be piped into rename_geom_aes() and you would have to set the scale_fill_*() for your renamed aesthetic. You'd probably get a warning about that the geom is ignoring unknown aesthetics, but I don't know if that can be helped.

Below is an example for making bucket 15 red.

library(tidyverse)
library(relayer) # https://github.com/clauswilke/relayer

ex <- df %>% 
  mutate(r0 = cutoff_values[-length(cutoff_values)],
         r = cutoff_values[-1]) %>% 
  mutate(x0 = 100, 
         y0 = 50)

ggplot(ex, aes(x0 = x0, y0 = y0, r0 = r0, r = r)) +
  ggforce::geom_arc_bar(aes(start = 0, end = 2 * pi, fill = value),
                        colour = NA) +
  ggforce::geom_arc_bar(data = ex[ex$bucket_id == 15,], # Whatever bucket you want
                        aes(start = 0, end = 2 * pi, fill2 = as.factor(bucket_id))) %>% 
  rename_geom_aes(new_aes = c("fill" = "fill2")) +
  scale_fill_manual(aesthetics = "fill2", values = "red", guide = "legend") +
  theme_void() +
  labs(fill = 'colour', fill2 = "highlight")

Question:

Continuing on from my previous bspline question

If this is my curve:

data <- tibble (
  x = c(10, 15, 17, 17, 20, 22, 22, 23, 25, 25, 27, 29),
  y = c(5, 7, 4, 4, 0, 5, 5, 6, 5, 5, 4, 5.5),
  g = c("A", "A", "A", "B", "B", "B", "C", "C", "C", "D","D","D"),
  pt = c(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1)
)

ggplot(data) + 
  stat_bspline2(aes(x=x, y=y, color = ..group.., group = g), size = 4, n = 300, geom = "bspline0") +
  scale_color_gradientn(colours = c("red", "pink", "green", "white"), guide = F) 

How do I add dots to selected points on the curve?

Here's how not to do it:

ggplot(data) + 
  stat_bspline2(aes(x=x, y=y, color = ..group.., group = g), size = 4, n = 300, geom = "bspline0") +
  scale_color_gradientn(colours = c("red", "pink", "green", "white"), guide = F) +
  stat_bspline2(data = pt, aes(x = x, y = x, color = ..group.., group = pt), n = 12, geom = "point", size = 9)
)

Answer:

It isn't perfect, but it works. Add some columns with the positions of the points you want (I'm assuming that if pt = 1, you want the point plotted)

data <- data %>%
    mutate(pt_x = ifelse(pt == 1, x, NA),
           pt_y = ifelse(pt == 1, y, NA))

ggplot(data) + 
    stat_bspline2(aes(x=x, y=y, color = ..group.., group = g), size = 4, n = 300, geom = "bspline0") +
    scale_color_gradientn(colours = c("red", "pink", "green", "white"), guide = F) +
    geom_point(aes(pt_x, pt_y))

Question:

I would like to change the colors of the voronoi polygons created by geom_voronoi_tile from the package ggforce, but was unable to do so. I tried with the following code:

library(tidyverse)
library(ggforce)
library(RColorBrewer)

col1 <- c("#d53e4f", "#f46d43", "#3288bd")
Species <- c("setosa", "versicolor", "virginica")

Tabla1 <- data.frame(Species, col1)
iris1 <- iris %>%
  left_join(Tabla1, by = "Species")


ggplot(iris1, aes(Sepal.Length, Sepal.Width) ) + 
  geom_voronoi_tile(aes(fill = Species, group = -1L, color = col1)) + 
  geom_voronoi_segment() +
  geom_point()

Here is my session info

R version 3.4.4 (2018-03-15)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS Sierra 10.12.6

Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods  
[7] base     

other attached packages:
 [1] RColorBrewer_1.1-2 ggforce_0.2.0.9000 agricolae_1.2-8   
 [4] mxmaps_0.1         forcats_0.4.0      stringr_1.4.0     
 [7] dplyr_0.8.0.1      purrr_0.3.2        readr_1.1.1       
[10] tidyr_0.8.2        tibble_2.0.1       ggplot2_3.1.0     
[13] tidyverse_1.2.1   

Answer:

To change the values of the polygon fill you can use scale_fill_manual() to set the colours to Species. Note that I dropped the color = col argument, as it would set the borders of a polygon to a colour, which in geom_voronoi_*() is handled by geom_voronoi_segment().

ggplot(iris1, aes(Sepal.Length, Sepal.Width) ) + 
  geom_voronoi_tile(aes(fill = Species, group = -1L)) + 
  geom_voronoi_segment() +
  scale_fill_manual(values = col1, breaks = Species) +
  geom_point()

To change the colours of the polygon edges you can set the aes(colour = Species) inside geom_voronoi_segment().

ggplot(iris1, aes(Sepal.Length, Sepal.Width) ) + 
  geom_voronoi_tile(aes(fill = Species, group = -1L)) + 
  geom_voronoi_segment(aes(colour = Species, group = -1L)) +
  geom_point() +
  scale_colour_manual(values = col1, breaks = Species)

Question:

I am intrigued by this plot of Albert Cairo.

I can smooth my curve sufficiently with ggforce::bspline

However, now that I don't have a date axis I am unsure as to how to change the color of a spline midway.

Let's assume that the three points represent the years 1990, 1991 and 1992. And someone got elected on July 1, 1990. I would like to change the color of the spline at this point. So the curved line would be red from origin until aprox (12, 5.6) then blue from (12, 5.6) to (17,4)

I am not sure how to accomplish this.

library(ggforce)
library(ggplot2)

data <- tibble (
  x = c(10, 15, 17),
  y = c(5, 7, 4)
)

ggplot(data) + 
  stat_bspline2(aes(x = x, y = y), n = 300,  geom = "bspline0", color = "red") +
  stat_bspline2(aes(x = x, y = y), n = 3,  geom = "point", color = "red") +
  geom_point(aes(x = x, y = y), color = "grey")

Thinking about what M.A. told me about groups I now have code that can:

Change the color of straight line segments:

# Works for straight lines
ggplot(data, aes(x=x, y=y, colour = g, group = 1)) + 
  geom_line(size = 3) + 
  geom_point() +
  scale_color_manual(values = c("A" = "red", "B" = "pink", "C" = "green", "D" = "white"))

And the continuous colour of a bspline. But I would like this to be discrete colors only as in the plot above.

# Works with continuous color
ggplot(data, aes(x=x, y=y, colour = g, group = 1)) + 
  geom_bspline2(size = 4, n = 300) +
  scale_color_manual(values = c("A" = "red", "B" = "pink", "C" = "green", "D" = "white"))

Or this error, "Error: Continuous value supplied to discrete scale" with:

ggplot(data) + 
  stat_bspline2(aes(x = x, y = y, color = ..group.., group = 1), n = 300,  geom = "bspline0") +
  scale_color_manual(values = c("A" = "red", "B" = "pink", "C" = "green", "D" = "white"))

So I'm wondering how to manually control the color of discrete segments with bspline.


Answer:

You can do this by grouping:

data <- tibble (
  x = c(10, 15, 17, 17, 20, 22),
  y = c(5, 7, 4, 4, 0, 5),
  g = c("A", "A", "A", "B", "B", "B")
)

ggplot(data) + 
  stat_bspline2(
                aes(x = x, y = y, color = ..group.., group = g), 
                n = 300,  geom = "bspline0") +
  scale_colour_gradient(low = "blue", high = "red", guide=FALSE) 

Edit:

The error Continuous value supplied to discrete scale is is somewhat confusing here. I don't know if there is an easier way to get what you want but it can be achieved using scale_colour_gradientn(). This function allows to map the group g to a gradient between n colours so you want n to be the number of groups.

For example, consider a larger data set with four groups:

# example data
data <- tibble (
  x = c(10, 15, 17, 17, 20, 22, 22, 23, 25, 25, 27, 29),
  y = c(5, 7, 4, 4, 0, 5, 5, 6, 5, 5, 4, 5.5),
  g = c("A", "A", "A", "B", "B", "B", "C", "C", "C", "D","D","D")
)

You can use a palette like rainbow() and specify the number of colours for the gradient to be 4 since there are four groups A, B, C and D.

# use a colour palette:
ggplot(data) + 
  stat_bspline2(
    aes(x = x, y = y, color = ..group.., group = g), 
    n = 300, size = 1,  geom = "bspline0") +
    scale_color_gradientn(colours = rainbow(4), 
                          guide = F
                          )

For custom colours, you may do the following:

# use custom colors:
ggplot(data, aes(x=x, y=y, color = ..group.., group = g)) + 
  geom_bspline2(size = 1, n = 300) +
  scale_color_gradientn(
    colours = c("red", "pink", "green", "white"),
    guide = F
    )

This uses a gradient between the colours red, pink, green and white. Note that the order of the colours matters as a different order leads to a different gradient and thus a different mapping of the groups.

Question:

I see how geom_area is used to fill the area under straight lines. How do I fill the area under a curve such as one created by geom_bspline?

library("tidyverse")
library("ggforce")

dftest <- tibble(
  x = c(1, 2, 3, 4, 5),
  y = c(10, 15, 30, 80, 5)
)

# Fill area under straight lines - OK
ggplot(dftest, aes(x = x, y = y)) +
  geom_point() +
  geom_line() +
  geom_area(alpha = 0.3)

# Fill area under curve ???
ggplot(dftest, aes(x = x, y = y)) +
  geom_point() +
  geom_bspline() 

Answer:

You could use the stat paired with the area geom:

ggplot(dftest, aes(x = x, y = y)) +
  geom_point() +
  stat_bspline(geom = "area", alpha = 0.3) 

Question:

I am playing with the ggforce library and I have difficulties with displaying long text. When the text to display is a bit longer, it disappears from the plot (see example #2). Is there a way to fix that?

library(tidyverse)
library(ggforce)
ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_circle(aes(color = Species, label = "Small sentence"),
    expand = unit(0.2, "mm")
  ) +
  geom_point() +
  theme(legend.position = "none") +
  theme_minimal()

This does not work

ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_circle(aes(color = Species, label = "This is a very long text to display"),
                   expand = unit(0.2, "mm")
  ) +
  geom_point() +
  theme(legend.position = "none") +
  theme_minimal()

Created on 2019-07-25 by the reprex package (v0.3.0)


Answer:

You can use stringr::str_wrap() in the tidyverse

ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_circle(
    aes(color = Species, label = str_wrap("This is a very long text to display", 20)),
    expand = unit(0.2, "mm")
  ) +
  geom_point() +
  theme(legend.position = "none") +
  theme_minimal()