Shift legend into empty facets of a faceted plot in ggplot2





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ height:90px;width:728px;box-sizing:border-box;
}







32















Consider the following plot:



library(ggplot2)

p <- ggplot(diamonds,
aes(x = carat, fill = cut)) +
geom_density(position = "stack") +
facet_wrap(~ color)


annotated facet_wrap plot



The facet_wrap function wraps a sequence of faceted panels into a roughly rectangular display of nrow rows and ncol columns. However, depending on the data, the actual number of panels is often a few panels short of nrow * ncol, which leaves a chunk of wasted space in the plot.



If the plot includes legend(s), the situation is exacerbated, because now we have even more wasted space due to the legend, whether it's on the right (default legend position), or one of the other three directions.



To save space, I would like to shift the legend(s) into the space created by unfilled facets.



The following works as a space-saving measure, but the legend is anchored to a corner of the plot area, with potentially a lot of space left on one side, creating an imbalanced look:



p +
theme(legend.position = c(1, 0),
legend.justification = c(1, 0))


legend anchored to a corner



Shifting a legend towards the centre of the blank space area by manually adjusting the legend.position/legend.justification values is a matter of trial and error, and difficult to scale if one has many faceted plots to work on.



In summary, I want a method that:





  1. Shifts the legend(s) of a faceted plot into the space created due to empty facets.

  2. Results in a reasonably nice-looking plot.

  3. Is easily automated to handle many plots.


This is a recurring use case for me, and I've decided to post it along with my working solution here in case anyone else finds it useful. I haven't seen this scenario asked/answered elsewhere on Stack Overflow. If anyone has, please leave a comment and I'll be happy to answer there instead or have this marked as a duplicate, as the case may be.










share|improve this question































    32















    Consider the following plot:



    library(ggplot2)

    p <- ggplot(diamonds,
    aes(x = carat, fill = cut)) +
    geom_density(position = "stack") +
    facet_wrap(~ color)


    annotated facet_wrap plot



    The facet_wrap function wraps a sequence of faceted panels into a roughly rectangular display of nrow rows and ncol columns. However, depending on the data, the actual number of panels is often a few panels short of nrow * ncol, which leaves a chunk of wasted space in the plot.



    If the plot includes legend(s), the situation is exacerbated, because now we have even more wasted space due to the legend, whether it's on the right (default legend position), or one of the other three directions.



    To save space, I would like to shift the legend(s) into the space created by unfilled facets.



    The following works as a space-saving measure, but the legend is anchored to a corner of the plot area, with potentially a lot of space left on one side, creating an imbalanced look:



    p +
    theme(legend.position = c(1, 0),
    legend.justification = c(1, 0))


    legend anchored to a corner



    Shifting a legend towards the centre of the blank space area by manually adjusting the legend.position/legend.justification values is a matter of trial and error, and difficult to scale if one has many faceted plots to work on.



    In summary, I want a method that:





    1. Shifts the legend(s) of a faceted plot into the space created due to empty facets.

    2. Results in a reasonably nice-looking plot.

    3. Is easily automated to handle many plots.


    This is a recurring use case for me, and I've decided to post it along with my working solution here in case anyone else finds it useful. I haven't seen this scenario asked/answered elsewhere on Stack Overflow. If anyone has, please leave a comment and I'll be happy to answer there instead or have this marked as a duplicate, as the case may be.










    share|improve this question



























      32












      32








      32


      16






      Consider the following plot:



      library(ggplot2)

      p <- ggplot(diamonds,
      aes(x = carat, fill = cut)) +
      geom_density(position = "stack") +
      facet_wrap(~ color)


      annotated facet_wrap plot



      The facet_wrap function wraps a sequence of faceted panels into a roughly rectangular display of nrow rows and ncol columns. However, depending on the data, the actual number of panels is often a few panels short of nrow * ncol, which leaves a chunk of wasted space in the plot.



      If the plot includes legend(s), the situation is exacerbated, because now we have even more wasted space due to the legend, whether it's on the right (default legend position), or one of the other three directions.



      To save space, I would like to shift the legend(s) into the space created by unfilled facets.



      The following works as a space-saving measure, but the legend is anchored to a corner of the plot area, with potentially a lot of space left on one side, creating an imbalanced look:



      p +
      theme(legend.position = c(1, 0),
      legend.justification = c(1, 0))


      legend anchored to a corner



      Shifting a legend towards the centre of the blank space area by manually adjusting the legend.position/legend.justification values is a matter of trial and error, and difficult to scale if one has many faceted plots to work on.



      In summary, I want a method that:





      1. Shifts the legend(s) of a faceted plot into the space created due to empty facets.

      2. Results in a reasonably nice-looking plot.

      3. Is easily automated to handle many plots.


      This is a recurring use case for me, and I've decided to post it along with my working solution here in case anyone else finds it useful. I haven't seen this scenario asked/answered elsewhere on Stack Overflow. If anyone has, please leave a comment and I'll be happy to answer there instead or have this marked as a duplicate, as the case may be.










      share|improve this question
















      Consider the following plot:



      library(ggplot2)

      p <- ggplot(diamonds,
      aes(x = carat, fill = cut)) +
      geom_density(position = "stack") +
      facet_wrap(~ color)


      annotated facet_wrap plot



      The facet_wrap function wraps a sequence of faceted panels into a roughly rectangular display of nrow rows and ncol columns. However, depending on the data, the actual number of panels is often a few panels short of nrow * ncol, which leaves a chunk of wasted space in the plot.



      If the plot includes legend(s), the situation is exacerbated, because now we have even more wasted space due to the legend, whether it's on the right (default legend position), or one of the other three directions.



      To save space, I would like to shift the legend(s) into the space created by unfilled facets.



      The following works as a space-saving measure, but the legend is anchored to a corner of the plot area, with potentially a lot of space left on one side, creating an imbalanced look:



      p +
      theme(legend.position = c(1, 0),
      legend.justification = c(1, 0))


      legend anchored to a corner



      Shifting a legend towards the centre of the blank space area by manually adjusting the legend.position/legend.justification values is a matter of trial and error, and difficult to scale if one has many faceted plots to work on.



      In summary, I want a method that:





      1. Shifts the legend(s) of a faceted plot into the space created due to empty facets.

      2. Results in a reasonably nice-looking plot.

      3. Is easily automated to handle many plots.


      This is a recurring use case for me, and I've decided to post it along with my working solution here in case anyone else finds it useful. I haven't seen this scenario asked/answered elsewhere on Stack Overflow. If anyone has, please leave a comment and I'll be happy to answer there instead or have this marked as a duplicate, as the case may be.







      r ggplot2






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Jan 30 at 20:01









      double-beep

      3,10241432




      3,10241432










      asked Jan 30 at 10:34









      Z.LinZ.Lin

      13.4k22239




      13.4k22239
























          2 Answers
          2






          active

          oldest

          votes


















          36














          The following is an extension to an answer I wrote for a previous question about utilising the space from empty facet panels, but I think it's sufficiently different to warrant its own space.



          Essentially, I wrote a function that takes a ggplot/grob object converted by ggplotGrob(), converts it to grob if it isn't one, and digs into the underlying grobs to move the legend grob into the cells that correspond to the empty space.



          Function:



          library(gtable)
          library(cowplot)

          shift_legend <- function(p){

          # check if p is a valid object
          if(!"gtable" %in% class(p)){
          if("ggplot" %in% class(p)){
          gp <- ggplotGrob(p) # convert to grob
          } else {
          message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
          return(p)
          }
          } else {
          gp <- p
          }

          # check for unfilled facet panels
          facet.panels <- grep("^panel", gp[["layout"]][["name"]])
          empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
          empty.facet.panels <- facet.panels[empty.facet.panels]
          if(length(empty.facet.panels) == 0){
          message("There are no unfilled facet panels to shift legend into. Returning original plot.")
          return(p)
          }

          # establish extent of unfilled facet panels (including any axis cells in between)
          empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
          empty.facet.panels <- list(min(empty.facet.panels[["t"]]), min(empty.facet.panels[["l"]]),
          max(empty.facet.panels[["b"]]), max(empty.facet.panels[["r"]]))
          names(empty.facet.panels) <- c("t", "l", "b", "r")

          # extract legend & copy over to location of unfilled facet panels
          guide.grob <- which(gp[["layout"]][["name"]] == "guide-box")
          if(length(guide.grob) == 0){
          message("There is no legend present. Returning original plot.")
          return(p)
          }
          gp <- gtable_add_grob(x = gp,
          grobs = gp[["grobs"]][[guide.grob]],
          t = empty.facet.panels[["t"]],
          l = empty.facet.panels[["l"]],
          b = empty.facet.panels[["b"]],
          r = empty.facet.panels[["r"]],
          name = "new-guide-box")

          # squash the original guide box's row / column (whichever applicable)
          # & empty its cell
          guide.grob <- gp[["layout"]][guide.grob, ]
          if(guide.grob[["l"]] == guide.grob[["r"]]){
          gp <- gtable_squash_cols(gp, cols = guide.grob[["l"]])
          }
          if(guide.grob[["t"]] == guide.grob[["b"]]){
          gp <- gtable_squash_rows(gp, rows = guide.grob[["t"]])
          }
          gp <- gtable_remove_grobs(gp, "guide-box")

          return(gp)
          }


          Result:



          library(grid)

          grid.draw(shift_legend(p))


          vertical legend result for p



          Nicer looking result if we take advantage of the empty space's direction to arrange the legend horizontally:



          p.new <- p +
          guides(fill = guide_legend(title.position = "top",
          label.position = "bottom",
          nrow = 1)) +
          theme(legend.direction = "horizontal")
          grid.draw(shift_legend(p.new))


          horizontal legend result for p.new



          Some other examples:



          # example 1: 1 empty panel, 1 vertical legend
          p1 <- ggplot(economics_long,
          aes(date, value, color = variable)) +
          geom_line() +
          facet_wrap(~ variable,
          scales = "free_y", nrow = 2,
          strip.position = "bottom") +
          theme(strip.background = element_blank(),
          strip.placement = "outside")
          grid.draw(shift_legend(p1))

          # example 2: 2 empty panels (vertically aligned) & 2 vertical legends side by side
          p2 <- ggplot(mpg,
          aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
          geom_point(size = 3) +
          facet_wrap(~ class, dir = "v") +
          theme(legend.box = "horizontal")
          grid.draw(shift_legend(p2))

          # example 3: facets in polar coordinates
          p3 <- ggplot(mtcars,
          aes(x = factor(1), fill = factor(cyl))) +
          geom_bar(width = 1, position = "fill") +
          facet_wrap(~ gear, nrow = 2) +
          coord_polar(theta = "y") +
          theme_void()
          grid.draw(shift_legend(p3))


          more illustrations






          share|improve this answer





















          • 5





            Very nice solution! Your inheritance tests (!"gtable" %in% class(p) etc) should be written via inherits: if (! inherits(p, 'gtable')). And since you do different things depending on the object class, consider using S3 methods instead of if statements.

            – Konrad Rudolph
            Jan 30 at 17:02



















          15





          +50









          Nice Q&A!



          I found something similar at this link. So, I thought that it would have been a nice addition to your function.



          More precisely the function reposition_legend() from lemon seems to be quite what you needed, except that it doesn't look for the empty spaces.



          I took inspiration from your function to find the names of the empty panels that are passed to reposition_legend() with the panel arg.



          Example data and libraries:



          library(ggplot2)
          library(gtable)
          library(lemon)

          p <- ggplot(diamonds,
          aes(x = carat, fill = cut)) +
          geom_density(position = "stack") +
          facet_wrap(~ color) +
          theme(legend.direction = "horizontal")


          Of course, I removed all the checks (if cases, which should be the same) just to concentrate on the important stuff.



          shift_legend2 <- function(p) {
          # ...
          # to grob
          gp <- ggplotGrob(p)
          facet.panels <- grep("^panel", gp[["layout"]][["name"]])
          empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
          empty.facet.panels <- facet.panels[empty.facet.panels]

          # establish name of empty panels
          empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
          names <- empty.facet.panels$name
          # example of names:
          #[1] "panel-3-2" "panel-3-3"

          # now we just need a simple call to reposition the legend
          reposition_legend(p, 'center', panel=names)
          }

          shift_legend2(p)


          enter image description here



          Note that this might still need some tweaking, I just thought it was something worth to be shared.



          At the moment the behaviour seems OK, and the function is a few lines shorter.





          Other cases.



          First example:



          p1 <- ggplot(economics_long, 
          aes(date, value, color = variable)) +
          geom_line() +
          facet_wrap(~ variable,
          scales = "free_y", nrow = 2,
          strip.position = "bottom") +
          theme(strip.background = element_blank(),
          strip.placement = "outside")

          shift_legend2(p1)


          enter image description here



          Second example:



          p2 <- ggplot(mpg,
          aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
          geom_point(size = 3) +
          facet_wrap(~ class, dir = "v") +
          theme(legend.box = "horizontal")

          #[1] "panel-2-3" "panel-3-3" are the names of empty panels in this case
          shift_legend2(p2)


          enter image description here



          Third example:



          p3 <- ggplot(mtcars, 
          aes(x = factor(1), fill = factor(cyl))) +
          geom_bar(width = 1, position = "fill") +
          facet_wrap(~ gear, nrow = 2) +
          coord_polar(theta = "y") +
          theme_void()
          shift_legend2(p3)


          enter image description here





          Complete function:



          shift_legend2 <- function(p) {
          # check if p is a valid object
          if(!(inherits(p, "gtable"))){
          if(inherits(p, "ggplot")){
          gp <- ggplotGrob(p) # convert to grob
          } else {
          message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
          return(p)
          }
          } else {
          gp <- p
          }

          # check for unfilled facet panels
          facet.panels <- grep("^panel", gp[["layout"]][["name"]])
          empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]),
          USE.NAMES = F)
          empty.facet.panels <- facet.panels[empty.facet.panels]

          if(length(empty.facet.panels) == 0){
          message("There are no unfilled facet panels to shift legend into. Returning original plot.")
          return(p)
          }

          # establish name of empty panels
          empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
          names <- empty.facet.panels$name

          # return repositioned legend
          reposition_legend(p, 'center', panel=names)
          }





          share|improve this answer


























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54438495%2fshift-legend-into-empty-facets-of-a-faceted-plot-in-ggplot2%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            36














            The following is an extension to an answer I wrote for a previous question about utilising the space from empty facet panels, but I think it's sufficiently different to warrant its own space.



            Essentially, I wrote a function that takes a ggplot/grob object converted by ggplotGrob(), converts it to grob if it isn't one, and digs into the underlying grobs to move the legend grob into the cells that correspond to the empty space.



            Function:



            library(gtable)
            library(cowplot)

            shift_legend <- function(p){

            # check if p is a valid object
            if(!"gtable" %in% class(p)){
            if("ggplot" %in% class(p)){
            gp <- ggplotGrob(p) # convert to grob
            } else {
            message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
            return(p)
            }
            } else {
            gp <- p
            }

            # check for unfilled facet panels
            facet.panels <- grep("^panel", gp[["layout"]][["name"]])
            empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
            empty.facet.panels <- facet.panels[empty.facet.panels]
            if(length(empty.facet.panels) == 0){
            message("There are no unfilled facet panels to shift legend into. Returning original plot.")
            return(p)
            }

            # establish extent of unfilled facet panels (including any axis cells in between)
            empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
            empty.facet.panels <- list(min(empty.facet.panels[["t"]]), min(empty.facet.panels[["l"]]),
            max(empty.facet.panels[["b"]]), max(empty.facet.panels[["r"]]))
            names(empty.facet.panels) <- c("t", "l", "b", "r")

            # extract legend & copy over to location of unfilled facet panels
            guide.grob <- which(gp[["layout"]][["name"]] == "guide-box")
            if(length(guide.grob) == 0){
            message("There is no legend present. Returning original plot.")
            return(p)
            }
            gp <- gtable_add_grob(x = gp,
            grobs = gp[["grobs"]][[guide.grob]],
            t = empty.facet.panels[["t"]],
            l = empty.facet.panels[["l"]],
            b = empty.facet.panels[["b"]],
            r = empty.facet.panels[["r"]],
            name = "new-guide-box")

            # squash the original guide box's row / column (whichever applicable)
            # & empty its cell
            guide.grob <- gp[["layout"]][guide.grob, ]
            if(guide.grob[["l"]] == guide.grob[["r"]]){
            gp <- gtable_squash_cols(gp, cols = guide.grob[["l"]])
            }
            if(guide.grob[["t"]] == guide.grob[["b"]]){
            gp <- gtable_squash_rows(gp, rows = guide.grob[["t"]])
            }
            gp <- gtable_remove_grobs(gp, "guide-box")

            return(gp)
            }


            Result:



            library(grid)

            grid.draw(shift_legend(p))


            vertical legend result for p



            Nicer looking result if we take advantage of the empty space's direction to arrange the legend horizontally:



            p.new <- p +
            guides(fill = guide_legend(title.position = "top",
            label.position = "bottom",
            nrow = 1)) +
            theme(legend.direction = "horizontal")
            grid.draw(shift_legend(p.new))


            horizontal legend result for p.new



            Some other examples:



            # example 1: 1 empty panel, 1 vertical legend
            p1 <- ggplot(economics_long,
            aes(date, value, color = variable)) +
            geom_line() +
            facet_wrap(~ variable,
            scales = "free_y", nrow = 2,
            strip.position = "bottom") +
            theme(strip.background = element_blank(),
            strip.placement = "outside")
            grid.draw(shift_legend(p1))

            # example 2: 2 empty panels (vertically aligned) & 2 vertical legends side by side
            p2 <- ggplot(mpg,
            aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
            geom_point(size = 3) +
            facet_wrap(~ class, dir = "v") +
            theme(legend.box = "horizontal")
            grid.draw(shift_legend(p2))

            # example 3: facets in polar coordinates
            p3 <- ggplot(mtcars,
            aes(x = factor(1), fill = factor(cyl))) +
            geom_bar(width = 1, position = "fill") +
            facet_wrap(~ gear, nrow = 2) +
            coord_polar(theta = "y") +
            theme_void()
            grid.draw(shift_legend(p3))


            more illustrations






            share|improve this answer





















            • 5





              Very nice solution! Your inheritance tests (!"gtable" %in% class(p) etc) should be written via inherits: if (! inherits(p, 'gtable')). And since you do different things depending on the object class, consider using S3 methods instead of if statements.

              – Konrad Rudolph
              Jan 30 at 17:02
















            36














            The following is an extension to an answer I wrote for a previous question about utilising the space from empty facet panels, but I think it's sufficiently different to warrant its own space.



            Essentially, I wrote a function that takes a ggplot/grob object converted by ggplotGrob(), converts it to grob if it isn't one, and digs into the underlying grobs to move the legend grob into the cells that correspond to the empty space.



            Function:



            library(gtable)
            library(cowplot)

            shift_legend <- function(p){

            # check if p is a valid object
            if(!"gtable" %in% class(p)){
            if("ggplot" %in% class(p)){
            gp <- ggplotGrob(p) # convert to grob
            } else {
            message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
            return(p)
            }
            } else {
            gp <- p
            }

            # check for unfilled facet panels
            facet.panels <- grep("^panel", gp[["layout"]][["name"]])
            empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
            empty.facet.panels <- facet.panels[empty.facet.panels]
            if(length(empty.facet.panels) == 0){
            message("There are no unfilled facet panels to shift legend into. Returning original plot.")
            return(p)
            }

            # establish extent of unfilled facet panels (including any axis cells in between)
            empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
            empty.facet.panels <- list(min(empty.facet.panels[["t"]]), min(empty.facet.panels[["l"]]),
            max(empty.facet.panels[["b"]]), max(empty.facet.panels[["r"]]))
            names(empty.facet.panels) <- c("t", "l", "b", "r")

            # extract legend & copy over to location of unfilled facet panels
            guide.grob <- which(gp[["layout"]][["name"]] == "guide-box")
            if(length(guide.grob) == 0){
            message("There is no legend present. Returning original plot.")
            return(p)
            }
            gp <- gtable_add_grob(x = gp,
            grobs = gp[["grobs"]][[guide.grob]],
            t = empty.facet.panels[["t"]],
            l = empty.facet.panels[["l"]],
            b = empty.facet.panels[["b"]],
            r = empty.facet.panels[["r"]],
            name = "new-guide-box")

            # squash the original guide box's row / column (whichever applicable)
            # & empty its cell
            guide.grob <- gp[["layout"]][guide.grob, ]
            if(guide.grob[["l"]] == guide.grob[["r"]]){
            gp <- gtable_squash_cols(gp, cols = guide.grob[["l"]])
            }
            if(guide.grob[["t"]] == guide.grob[["b"]]){
            gp <- gtable_squash_rows(gp, rows = guide.grob[["t"]])
            }
            gp <- gtable_remove_grobs(gp, "guide-box")

            return(gp)
            }


            Result:



            library(grid)

            grid.draw(shift_legend(p))


            vertical legend result for p



            Nicer looking result if we take advantage of the empty space's direction to arrange the legend horizontally:



            p.new <- p +
            guides(fill = guide_legend(title.position = "top",
            label.position = "bottom",
            nrow = 1)) +
            theme(legend.direction = "horizontal")
            grid.draw(shift_legend(p.new))


            horizontal legend result for p.new



            Some other examples:



            # example 1: 1 empty panel, 1 vertical legend
            p1 <- ggplot(economics_long,
            aes(date, value, color = variable)) +
            geom_line() +
            facet_wrap(~ variable,
            scales = "free_y", nrow = 2,
            strip.position = "bottom") +
            theme(strip.background = element_blank(),
            strip.placement = "outside")
            grid.draw(shift_legend(p1))

            # example 2: 2 empty panels (vertically aligned) & 2 vertical legends side by side
            p2 <- ggplot(mpg,
            aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
            geom_point(size = 3) +
            facet_wrap(~ class, dir = "v") +
            theme(legend.box = "horizontal")
            grid.draw(shift_legend(p2))

            # example 3: facets in polar coordinates
            p3 <- ggplot(mtcars,
            aes(x = factor(1), fill = factor(cyl))) +
            geom_bar(width = 1, position = "fill") +
            facet_wrap(~ gear, nrow = 2) +
            coord_polar(theta = "y") +
            theme_void()
            grid.draw(shift_legend(p3))


            more illustrations






            share|improve this answer





















            • 5





              Very nice solution! Your inheritance tests (!"gtable" %in% class(p) etc) should be written via inherits: if (! inherits(p, 'gtable')). And since you do different things depending on the object class, consider using S3 methods instead of if statements.

              – Konrad Rudolph
              Jan 30 at 17:02














            36












            36








            36







            The following is an extension to an answer I wrote for a previous question about utilising the space from empty facet panels, but I think it's sufficiently different to warrant its own space.



            Essentially, I wrote a function that takes a ggplot/grob object converted by ggplotGrob(), converts it to grob if it isn't one, and digs into the underlying grobs to move the legend grob into the cells that correspond to the empty space.



            Function:



            library(gtable)
            library(cowplot)

            shift_legend <- function(p){

            # check if p is a valid object
            if(!"gtable" %in% class(p)){
            if("ggplot" %in% class(p)){
            gp <- ggplotGrob(p) # convert to grob
            } else {
            message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
            return(p)
            }
            } else {
            gp <- p
            }

            # check for unfilled facet panels
            facet.panels <- grep("^panel", gp[["layout"]][["name"]])
            empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
            empty.facet.panels <- facet.panels[empty.facet.panels]
            if(length(empty.facet.panels) == 0){
            message("There are no unfilled facet panels to shift legend into. Returning original plot.")
            return(p)
            }

            # establish extent of unfilled facet panels (including any axis cells in between)
            empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
            empty.facet.panels <- list(min(empty.facet.panels[["t"]]), min(empty.facet.panels[["l"]]),
            max(empty.facet.panels[["b"]]), max(empty.facet.panels[["r"]]))
            names(empty.facet.panels) <- c("t", "l", "b", "r")

            # extract legend & copy over to location of unfilled facet panels
            guide.grob <- which(gp[["layout"]][["name"]] == "guide-box")
            if(length(guide.grob) == 0){
            message("There is no legend present. Returning original plot.")
            return(p)
            }
            gp <- gtable_add_grob(x = gp,
            grobs = gp[["grobs"]][[guide.grob]],
            t = empty.facet.panels[["t"]],
            l = empty.facet.panels[["l"]],
            b = empty.facet.panels[["b"]],
            r = empty.facet.panels[["r"]],
            name = "new-guide-box")

            # squash the original guide box's row / column (whichever applicable)
            # & empty its cell
            guide.grob <- gp[["layout"]][guide.grob, ]
            if(guide.grob[["l"]] == guide.grob[["r"]]){
            gp <- gtable_squash_cols(gp, cols = guide.grob[["l"]])
            }
            if(guide.grob[["t"]] == guide.grob[["b"]]){
            gp <- gtable_squash_rows(gp, rows = guide.grob[["t"]])
            }
            gp <- gtable_remove_grobs(gp, "guide-box")

            return(gp)
            }


            Result:



            library(grid)

            grid.draw(shift_legend(p))


            vertical legend result for p



            Nicer looking result if we take advantage of the empty space's direction to arrange the legend horizontally:



            p.new <- p +
            guides(fill = guide_legend(title.position = "top",
            label.position = "bottom",
            nrow = 1)) +
            theme(legend.direction = "horizontal")
            grid.draw(shift_legend(p.new))


            horizontal legend result for p.new



            Some other examples:



            # example 1: 1 empty panel, 1 vertical legend
            p1 <- ggplot(economics_long,
            aes(date, value, color = variable)) +
            geom_line() +
            facet_wrap(~ variable,
            scales = "free_y", nrow = 2,
            strip.position = "bottom") +
            theme(strip.background = element_blank(),
            strip.placement = "outside")
            grid.draw(shift_legend(p1))

            # example 2: 2 empty panels (vertically aligned) & 2 vertical legends side by side
            p2 <- ggplot(mpg,
            aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
            geom_point(size = 3) +
            facet_wrap(~ class, dir = "v") +
            theme(legend.box = "horizontal")
            grid.draw(shift_legend(p2))

            # example 3: facets in polar coordinates
            p3 <- ggplot(mtcars,
            aes(x = factor(1), fill = factor(cyl))) +
            geom_bar(width = 1, position = "fill") +
            facet_wrap(~ gear, nrow = 2) +
            coord_polar(theta = "y") +
            theme_void()
            grid.draw(shift_legend(p3))


            more illustrations






            share|improve this answer















            The following is an extension to an answer I wrote for a previous question about utilising the space from empty facet panels, but I think it's sufficiently different to warrant its own space.



            Essentially, I wrote a function that takes a ggplot/grob object converted by ggplotGrob(), converts it to grob if it isn't one, and digs into the underlying grobs to move the legend grob into the cells that correspond to the empty space.



            Function:



            library(gtable)
            library(cowplot)

            shift_legend <- function(p){

            # check if p is a valid object
            if(!"gtable" %in% class(p)){
            if("ggplot" %in% class(p)){
            gp <- ggplotGrob(p) # convert to grob
            } else {
            message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
            return(p)
            }
            } else {
            gp <- p
            }

            # check for unfilled facet panels
            facet.panels <- grep("^panel", gp[["layout"]][["name"]])
            empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
            empty.facet.panels <- facet.panels[empty.facet.panels]
            if(length(empty.facet.panels) == 0){
            message("There are no unfilled facet panels to shift legend into. Returning original plot.")
            return(p)
            }

            # establish extent of unfilled facet panels (including any axis cells in between)
            empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
            empty.facet.panels <- list(min(empty.facet.panels[["t"]]), min(empty.facet.panels[["l"]]),
            max(empty.facet.panels[["b"]]), max(empty.facet.panels[["r"]]))
            names(empty.facet.panels) <- c("t", "l", "b", "r")

            # extract legend & copy over to location of unfilled facet panels
            guide.grob <- which(gp[["layout"]][["name"]] == "guide-box")
            if(length(guide.grob) == 0){
            message("There is no legend present. Returning original plot.")
            return(p)
            }
            gp <- gtable_add_grob(x = gp,
            grobs = gp[["grobs"]][[guide.grob]],
            t = empty.facet.panels[["t"]],
            l = empty.facet.panels[["l"]],
            b = empty.facet.panels[["b"]],
            r = empty.facet.panels[["r"]],
            name = "new-guide-box")

            # squash the original guide box's row / column (whichever applicable)
            # & empty its cell
            guide.grob <- gp[["layout"]][guide.grob, ]
            if(guide.grob[["l"]] == guide.grob[["r"]]){
            gp <- gtable_squash_cols(gp, cols = guide.grob[["l"]])
            }
            if(guide.grob[["t"]] == guide.grob[["b"]]){
            gp <- gtable_squash_rows(gp, rows = guide.grob[["t"]])
            }
            gp <- gtable_remove_grobs(gp, "guide-box")

            return(gp)
            }


            Result:



            library(grid)

            grid.draw(shift_legend(p))


            vertical legend result for p



            Nicer looking result if we take advantage of the empty space's direction to arrange the legend horizontally:



            p.new <- p +
            guides(fill = guide_legend(title.position = "top",
            label.position = "bottom",
            nrow = 1)) +
            theme(legend.direction = "horizontal")
            grid.draw(shift_legend(p.new))


            horizontal legend result for p.new



            Some other examples:



            # example 1: 1 empty panel, 1 vertical legend
            p1 <- ggplot(economics_long,
            aes(date, value, color = variable)) +
            geom_line() +
            facet_wrap(~ variable,
            scales = "free_y", nrow = 2,
            strip.position = "bottom") +
            theme(strip.background = element_blank(),
            strip.placement = "outside")
            grid.draw(shift_legend(p1))

            # example 2: 2 empty panels (vertically aligned) & 2 vertical legends side by side
            p2 <- ggplot(mpg,
            aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
            geom_point(size = 3) +
            facet_wrap(~ class, dir = "v") +
            theme(legend.box = "horizontal")
            grid.draw(shift_legend(p2))

            # example 3: facets in polar coordinates
            p3 <- ggplot(mtcars,
            aes(x = factor(1), fill = factor(cyl))) +
            geom_bar(width = 1, position = "fill") +
            facet_wrap(~ gear, nrow = 2) +
            coord_polar(theta = "y") +
            theme_void()
            grid.draw(shift_legend(p3))


            more illustrations







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Jan 30 at 20:05









            double-beep

            3,10241432




            3,10241432










            answered Jan 30 at 10:34









            Z.LinZ.Lin

            13.4k22239




            13.4k22239








            • 5





              Very nice solution! Your inheritance tests (!"gtable" %in% class(p) etc) should be written via inherits: if (! inherits(p, 'gtable')). And since you do different things depending on the object class, consider using S3 methods instead of if statements.

              – Konrad Rudolph
              Jan 30 at 17:02














            • 5





              Very nice solution! Your inheritance tests (!"gtable" %in% class(p) etc) should be written via inherits: if (! inherits(p, 'gtable')). And since you do different things depending on the object class, consider using S3 methods instead of if statements.

              – Konrad Rudolph
              Jan 30 at 17:02








            5




            5





            Very nice solution! Your inheritance tests (!"gtable" %in% class(p) etc) should be written via inherits: if (! inherits(p, 'gtable')). And since you do different things depending on the object class, consider using S3 methods instead of if statements.

            – Konrad Rudolph
            Jan 30 at 17:02





            Very nice solution! Your inheritance tests (!"gtable" %in% class(p) etc) should be written via inherits: if (! inherits(p, 'gtable')). And since you do different things depending on the object class, consider using S3 methods instead of if statements.

            – Konrad Rudolph
            Jan 30 at 17:02













            15





            +50









            Nice Q&A!



            I found something similar at this link. So, I thought that it would have been a nice addition to your function.



            More precisely the function reposition_legend() from lemon seems to be quite what you needed, except that it doesn't look for the empty spaces.



            I took inspiration from your function to find the names of the empty panels that are passed to reposition_legend() with the panel arg.



            Example data and libraries:



            library(ggplot2)
            library(gtable)
            library(lemon)

            p <- ggplot(diamonds,
            aes(x = carat, fill = cut)) +
            geom_density(position = "stack") +
            facet_wrap(~ color) +
            theme(legend.direction = "horizontal")


            Of course, I removed all the checks (if cases, which should be the same) just to concentrate on the important stuff.



            shift_legend2 <- function(p) {
            # ...
            # to grob
            gp <- ggplotGrob(p)
            facet.panels <- grep("^panel", gp[["layout"]][["name"]])
            empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
            empty.facet.panels <- facet.panels[empty.facet.panels]

            # establish name of empty panels
            empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
            names <- empty.facet.panels$name
            # example of names:
            #[1] "panel-3-2" "panel-3-3"

            # now we just need a simple call to reposition the legend
            reposition_legend(p, 'center', panel=names)
            }

            shift_legend2(p)


            enter image description here



            Note that this might still need some tweaking, I just thought it was something worth to be shared.



            At the moment the behaviour seems OK, and the function is a few lines shorter.





            Other cases.



            First example:



            p1 <- ggplot(economics_long, 
            aes(date, value, color = variable)) +
            geom_line() +
            facet_wrap(~ variable,
            scales = "free_y", nrow = 2,
            strip.position = "bottom") +
            theme(strip.background = element_blank(),
            strip.placement = "outside")

            shift_legend2(p1)


            enter image description here



            Second example:



            p2 <- ggplot(mpg,
            aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
            geom_point(size = 3) +
            facet_wrap(~ class, dir = "v") +
            theme(legend.box = "horizontal")

            #[1] "panel-2-3" "panel-3-3" are the names of empty panels in this case
            shift_legend2(p2)


            enter image description here



            Third example:



            p3 <- ggplot(mtcars, 
            aes(x = factor(1), fill = factor(cyl))) +
            geom_bar(width = 1, position = "fill") +
            facet_wrap(~ gear, nrow = 2) +
            coord_polar(theta = "y") +
            theme_void()
            shift_legend2(p3)


            enter image description here





            Complete function:



            shift_legend2 <- function(p) {
            # check if p is a valid object
            if(!(inherits(p, "gtable"))){
            if(inherits(p, "ggplot")){
            gp <- ggplotGrob(p) # convert to grob
            } else {
            message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
            return(p)
            }
            } else {
            gp <- p
            }

            # check for unfilled facet panels
            facet.panels <- grep("^panel", gp[["layout"]][["name"]])
            empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]),
            USE.NAMES = F)
            empty.facet.panels <- facet.panels[empty.facet.panels]

            if(length(empty.facet.panels) == 0){
            message("There are no unfilled facet panels to shift legend into. Returning original plot.")
            return(p)
            }

            # establish name of empty panels
            empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
            names <- empty.facet.panels$name

            # return repositioned legend
            reposition_legend(p, 'center', panel=names)
            }





            share|improve this answer






























              15





              +50









              Nice Q&A!



              I found something similar at this link. So, I thought that it would have been a nice addition to your function.



              More precisely the function reposition_legend() from lemon seems to be quite what you needed, except that it doesn't look for the empty spaces.



              I took inspiration from your function to find the names of the empty panels that are passed to reposition_legend() with the panel arg.



              Example data and libraries:



              library(ggplot2)
              library(gtable)
              library(lemon)

              p <- ggplot(diamonds,
              aes(x = carat, fill = cut)) +
              geom_density(position = "stack") +
              facet_wrap(~ color) +
              theme(legend.direction = "horizontal")


              Of course, I removed all the checks (if cases, which should be the same) just to concentrate on the important stuff.



              shift_legend2 <- function(p) {
              # ...
              # to grob
              gp <- ggplotGrob(p)
              facet.panels <- grep("^panel", gp[["layout"]][["name"]])
              empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
              empty.facet.panels <- facet.panels[empty.facet.panels]

              # establish name of empty panels
              empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
              names <- empty.facet.panels$name
              # example of names:
              #[1] "panel-3-2" "panel-3-3"

              # now we just need a simple call to reposition the legend
              reposition_legend(p, 'center', panel=names)
              }

              shift_legend2(p)


              enter image description here



              Note that this might still need some tweaking, I just thought it was something worth to be shared.



              At the moment the behaviour seems OK, and the function is a few lines shorter.





              Other cases.



              First example:



              p1 <- ggplot(economics_long, 
              aes(date, value, color = variable)) +
              geom_line() +
              facet_wrap(~ variable,
              scales = "free_y", nrow = 2,
              strip.position = "bottom") +
              theme(strip.background = element_blank(),
              strip.placement = "outside")

              shift_legend2(p1)


              enter image description here



              Second example:



              p2 <- ggplot(mpg,
              aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
              geom_point(size = 3) +
              facet_wrap(~ class, dir = "v") +
              theme(legend.box = "horizontal")

              #[1] "panel-2-3" "panel-3-3" are the names of empty panels in this case
              shift_legend2(p2)


              enter image description here



              Third example:



              p3 <- ggplot(mtcars, 
              aes(x = factor(1), fill = factor(cyl))) +
              geom_bar(width = 1, position = "fill") +
              facet_wrap(~ gear, nrow = 2) +
              coord_polar(theta = "y") +
              theme_void()
              shift_legend2(p3)


              enter image description here





              Complete function:



              shift_legend2 <- function(p) {
              # check if p is a valid object
              if(!(inherits(p, "gtable"))){
              if(inherits(p, "ggplot")){
              gp <- ggplotGrob(p) # convert to grob
              } else {
              message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
              return(p)
              }
              } else {
              gp <- p
              }

              # check for unfilled facet panels
              facet.panels <- grep("^panel", gp[["layout"]][["name"]])
              empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]),
              USE.NAMES = F)
              empty.facet.panels <- facet.panels[empty.facet.panels]

              if(length(empty.facet.panels) == 0){
              message("There are no unfilled facet panels to shift legend into. Returning original plot.")
              return(p)
              }

              # establish name of empty panels
              empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
              names <- empty.facet.panels$name

              # return repositioned legend
              reposition_legend(p, 'center', panel=names)
              }





              share|improve this answer




























                15





                +50







                15





                +50



                15




                +50





                Nice Q&A!



                I found something similar at this link. So, I thought that it would have been a nice addition to your function.



                More precisely the function reposition_legend() from lemon seems to be quite what you needed, except that it doesn't look for the empty spaces.



                I took inspiration from your function to find the names of the empty panels that are passed to reposition_legend() with the panel arg.



                Example data and libraries:



                library(ggplot2)
                library(gtable)
                library(lemon)

                p <- ggplot(diamonds,
                aes(x = carat, fill = cut)) +
                geom_density(position = "stack") +
                facet_wrap(~ color) +
                theme(legend.direction = "horizontal")


                Of course, I removed all the checks (if cases, which should be the same) just to concentrate on the important stuff.



                shift_legend2 <- function(p) {
                # ...
                # to grob
                gp <- ggplotGrob(p)
                facet.panels <- grep("^panel", gp[["layout"]][["name"]])
                empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
                empty.facet.panels <- facet.panels[empty.facet.panels]

                # establish name of empty panels
                empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
                names <- empty.facet.panels$name
                # example of names:
                #[1] "panel-3-2" "panel-3-3"

                # now we just need a simple call to reposition the legend
                reposition_legend(p, 'center', panel=names)
                }

                shift_legend2(p)


                enter image description here



                Note that this might still need some tweaking, I just thought it was something worth to be shared.



                At the moment the behaviour seems OK, and the function is a few lines shorter.





                Other cases.



                First example:



                p1 <- ggplot(economics_long, 
                aes(date, value, color = variable)) +
                geom_line() +
                facet_wrap(~ variable,
                scales = "free_y", nrow = 2,
                strip.position = "bottom") +
                theme(strip.background = element_blank(),
                strip.placement = "outside")

                shift_legend2(p1)


                enter image description here



                Second example:



                p2 <- ggplot(mpg,
                aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
                geom_point(size = 3) +
                facet_wrap(~ class, dir = "v") +
                theme(legend.box = "horizontal")

                #[1] "panel-2-3" "panel-3-3" are the names of empty panels in this case
                shift_legend2(p2)


                enter image description here



                Third example:



                p3 <- ggplot(mtcars, 
                aes(x = factor(1), fill = factor(cyl))) +
                geom_bar(width = 1, position = "fill") +
                facet_wrap(~ gear, nrow = 2) +
                coord_polar(theta = "y") +
                theme_void()
                shift_legend2(p3)


                enter image description here





                Complete function:



                shift_legend2 <- function(p) {
                # check if p is a valid object
                if(!(inherits(p, "gtable"))){
                if(inherits(p, "ggplot")){
                gp <- ggplotGrob(p) # convert to grob
                } else {
                message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
                return(p)
                }
                } else {
                gp <- p
                }

                # check for unfilled facet panels
                facet.panels <- grep("^panel", gp[["layout"]][["name"]])
                empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]),
                USE.NAMES = F)
                empty.facet.panels <- facet.panels[empty.facet.panels]

                if(length(empty.facet.panels) == 0){
                message("There are no unfilled facet panels to shift legend into. Returning original plot.")
                return(p)
                }

                # establish name of empty panels
                empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
                names <- empty.facet.panels$name

                # return repositioned legend
                reposition_legend(p, 'center', panel=names)
                }





                share|improve this answer















                Nice Q&A!



                I found something similar at this link. So, I thought that it would have been a nice addition to your function.



                More precisely the function reposition_legend() from lemon seems to be quite what you needed, except that it doesn't look for the empty spaces.



                I took inspiration from your function to find the names of the empty panels that are passed to reposition_legend() with the panel arg.



                Example data and libraries:



                library(ggplot2)
                library(gtable)
                library(lemon)

                p <- ggplot(diamonds,
                aes(x = carat, fill = cut)) +
                geom_density(position = "stack") +
                facet_wrap(~ color) +
                theme(legend.direction = "horizontal")


                Of course, I removed all the checks (if cases, which should be the same) just to concentrate on the important stuff.



                shift_legend2 <- function(p) {
                # ...
                # to grob
                gp <- ggplotGrob(p)
                facet.panels <- grep("^panel", gp[["layout"]][["name"]])
                empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]))
                empty.facet.panels <- facet.panels[empty.facet.panels]

                # establish name of empty panels
                empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
                names <- empty.facet.panels$name
                # example of names:
                #[1] "panel-3-2" "panel-3-3"

                # now we just need a simple call to reposition the legend
                reposition_legend(p, 'center', panel=names)
                }

                shift_legend2(p)


                enter image description here



                Note that this might still need some tweaking, I just thought it was something worth to be shared.



                At the moment the behaviour seems OK, and the function is a few lines shorter.





                Other cases.



                First example:



                p1 <- ggplot(economics_long, 
                aes(date, value, color = variable)) +
                geom_line() +
                facet_wrap(~ variable,
                scales = "free_y", nrow = 2,
                strip.position = "bottom") +
                theme(strip.background = element_blank(),
                strip.placement = "outside")

                shift_legend2(p1)


                enter image description here



                Second example:



                p2 <- ggplot(mpg,
                aes(x = displ, y = hwy, color = fl, shape = factor(cyl))) +
                geom_point(size = 3) +
                facet_wrap(~ class, dir = "v") +
                theme(legend.box = "horizontal")

                #[1] "panel-2-3" "panel-3-3" are the names of empty panels in this case
                shift_legend2(p2)


                enter image description here



                Third example:



                p3 <- ggplot(mtcars, 
                aes(x = factor(1), fill = factor(cyl))) +
                geom_bar(width = 1, position = "fill") +
                facet_wrap(~ gear, nrow = 2) +
                coord_polar(theta = "y") +
                theme_void()
                shift_legend2(p3)


                enter image description here





                Complete function:



                shift_legend2 <- function(p) {
                # check if p is a valid object
                if(!(inherits(p, "gtable"))){
                if(inherits(p, "ggplot")){
                gp <- ggplotGrob(p) # convert to grob
                } else {
                message("This is neither a ggplot object nor a grob generated from ggplotGrob. Returning original plot.")
                return(p)
                }
                } else {
                gp <- p
                }

                # check for unfilled facet panels
                facet.panels <- grep("^panel", gp[["layout"]][["name"]])
                empty.facet.panels <- sapply(facet.panels, function(i) "zeroGrob" %in% class(gp[["grobs"]][[i]]),
                USE.NAMES = F)
                empty.facet.panels <- facet.panels[empty.facet.panels]

                if(length(empty.facet.panels) == 0){
                message("There are no unfilled facet panels to shift legend into. Returning original plot.")
                return(p)
                }

                # establish name of empty panels
                empty.facet.panels <- gp[["layout"]][empty.facet.panels, ]
                names <- empty.facet.panels$name

                # return repositioned legend
                reposition_legend(p, 'center', panel=names)
                }






                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Feb 1 at 8:31

























                answered Jan 30 at 15:24









                RLaveRLave

                5,28911226




                5,28911226






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54438495%2fshift-legend-into-empty-facets-of-a-faceted-plot-in-ggplot2%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Can a sorcerer learn a 5th-level spell early by creating spell slots using the Font of Magic feature?

                    ts Property 'filter' does not exist on type '{}'

                    mat-slide-toggle shouldn't change it's state when I click cancel in confirmation window