---
title: "Set Axis Break for ggplot2"
author: "Guangchuang Yu and Shuangbin Xu\\

        School of Basic Medical Sciences, Southern Medical University"
date: "`r Sys.Date()`"
output:
  prettydoc::html_pretty:
    toc: true
    theme: cayman
    highlight: github
  pdf_document:
    toc: true
vignette: >
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteIndexEntry{ggbreak introduction}
  %\VignetteDepends{ggplot2}
  %\VignetteDepends{grid}
  %\VignetteDepends{aplot}
  %\VignetteDepends{ggplotify}
  %\usepackage[utf8]{inputenc}
---



```{r include=FALSE}
knitr::opts_chunk$set(warning = FALSE,
                      message = TRUE)

library(ggplot2)
library(ggbreak)
library(patchwork)
library(yulab.utils)
```

## Introduction

This package was first designed to set breakpoints for truncating the plot as I need to [shrink outlier long branch of a phylogenetic tree](https://yulab-smu.top/treedata-book/faq.html#shrink-outlier-long-branch).


Axis break or a so-called gap plot is useful for large datasets that are not normally distributed and contain outliers. Sometimes we can transform the data (e.g. using log-transformation if the data was log-normal distributed) to solve this problem. But this is not always granted. The data may just simply contain outliers and these outliers are meaningful. A simple gap plot can solve this issue well to present the data in detail with both normal and extreme data. 


This package provides several scale functions to break down a 'gg' plot into pieces and align them together with (gap plot) or without (wrap plot or cut plot) ignoring subplots. Our methods are fully compatible with `ggplot2`, so that users can still use the `+` operator to add geometric layers after creating a broken axis.



If you use `r CRANpkg('ggbreak')` in published research, please cite the following paper:

+ S Xu<sup>#</sup>, M Chen<sup>#</sup>, T Feng, L Zhan, L Zhou, __G Yu__<sup>\*</sup>. Use ggbreak to effectively utilize plotting space to deal with large datasets and outliers. __*Frontiers in Genetics*__. 2021, 12:774846. doi: [10.3389/fgene.2021.774846](https://www.frontiersin.org/articles/10.3389/fgene.2021.774846/full/)


## Gap plot

For creating gap plot, we provide `scale_x_break` and `scale_y_break` functions. Multiple breakpoints on a single axis are supported, and you can also apply both functions to set breakpoints for both x and y axes simultaneously.

### Feature 1: Compatible with ggplot2

After breaking the plot, we can still superpose geometric layers and set themes. This ensures that users familiar with `ggplot2` can seamlessly adopt `ggbreak` without changing their workflow. The following example demonstrates adding a text layer and modifying the theme after applying an axis break.

```{r fig.keep="last"}
library(ggplot2)
library(ggbreak) 
library(patchwork)

set.seed(2019-01-19)
d <- data.frame(x = 1:20,
   y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22)
)
 
p1 <- ggplot(d, aes(y, x)) + geom_col(orientation="y")
d2 <- data.frame(x = c(2, 18), y = c(7, 26), label = c("hello", "world"))
p2 <- p1 + scale_x_break(c(7, 17)) + 
  geom_text(aes(y, x, label=label), data=d2, hjust=1, colour = 'firebrick')  + 
  xlab(NULL) + ylab(NULL) + theme_minimal()

p1 + p2
```

### Feature 2: Multiple break-points are supported

`ggbreak` allows users to specify multiple breakpoints on a single axis. This is particularly useful when the data contains multiple clusters of outliers or interesting regions separated by large empty intervals. You can simply add multiple `scale_x_break()` layers to the plot.

```{r}
p2 + scale_x_break(c(18, 21))
```

### Feature 3: Simultaneous breaks on both x and y axes

You can combine `scale_x_break()` and `scale_y_break()` to create dual-axis break plots. This is particularly useful when you have data points that form distinct clusters with large gaps in between on both dimensions. The package handles the layout and alignment automatically.

```{r fig.width=8, fig.height=6}
set.seed(2023-01-01)
df <- data.frame(
    x = c(rnorm(50, 5, 1), rnorm(10, 50, 2)),
    y = c(rnorm(50, 10, 2), rnorm(10, 100, 5)),
    group = c(rep("Cluster 1", 50), rep("Cluster 2", 10))
)

ggplot(df, aes(x, y, color = group)) + 
    geom_point(size = 3) +
    scale_x_break(c(10, 45)) + 
    scale_y_break(c(20, 90)) +
    theme_bw() +
    theme(legend.position = "top")
```

Multiple breaks on both axes are also supported. This feature enables complex visualizations where data is distributed across multiple disjoint regions in a 2D space.

```{r fig.width=8, fig.height=6}
ggplot(df, aes(x, y, color = group)) + 
    geom_point(size = 3) +
    scale_x_break(c(10, 20)) + 
    scale_x_break(c(30, 45)) + 
    scale_y_break(c(20, 40)) + 
    scale_y_break(c(60, 90)) +
    theme_bw() +
    theme(legend.position = "top")
```

### Feature 4: Axis break symbols

You can add standard axis break symbols (like a double-slash `//`) to the axes where they are broken by using the `symbol` parameter. This visual cue helps readers quickly identify that the axis is discontinuous. Currently, `symbol = "slash"` is supported, and it works for both single and dual axis breaks.

```{r fig.width=8, fig.height=4}
ggplot(mpg, aes(displ, hwy)) +
    geom_point() +
    scale_x_break(c(3, 4), symbol = "slash")
```

### Feature 5: Zoom in or zoom out of subplots

The `scales` parameter allows you to control the relative size of the subplots. This is useful when you want to zoom in on a specific range of data to show more detail, or zoom out to show the overall trend. A value larger than 1 zooms in (allocates more space), while a value smaller than 1 zooms out.

```{r}
p1 + scale_x_break(c(7, 17), scales = 1.5) + scale_x_break(c(18, 21), scales=2)
```

### Feature 6: Support reverse scale

`ggbreak` works seamlessly with `scale_y_reverse()` (and `scale_x_reverse()`). This is common in fields like oceanography or atmospheric science where depth or pressure is plotted on a reversed axis. The break function respects the reversed direction of the axis.

```{r fig.keep='last'}
g <- ggplot(d, aes(x, y)) + geom_col()
g2 <- g + scale_y_break(c(7, 17), scales = 1.5) + 
  scale_y_break(c(18, 21), scale=2) + scale_y_reverse()
g + g2
```

### Feature 7: Compatible with scale transform functions

Users can apply scale transform functions, such as `scale_x_log10` and `scale_x_sqrt`, to an axis break plot. This allows for handling data that spans several orders of magnitude while still excluding uninteresting ranges.

```{r fig.keep='last', fig.width=10, fig.height=5}
p2 <- p1 + scale_x_break(c(7, 17)) 
p3 <- p1 + scale_x_break(c(7, 17)) + scale_x_log10()
p2 + p3
```

### Feature 8: Compatible with `coord_flip`

Flipping the coordinate system with `coord_flip()` is fully supported. This is often used to create horizontal bar charts or to swap axes for better readability. `ggbreak` detects the flip and adjusts the axis breaks accordingly.

```{r message=FALSE}
g + coord_flip() + scale_y_break(c(7, 18))
```


### Feature 9: Compatible with `facet_grid` and `facet_wrap`

`ggbreak` can be used in conjunction with faceting functions like `facet_grid()` and `facet_wrap()`. This allows you to create small multiples where each panel has a broken axis, which is extremely powerful for comparing distributions across different groups.

```{r message=FALSE}
set.seed(2019-01-19)
d <- data.frame(
  x = 1:20,
  y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22),
  group = c(rep("A", 10), rep("B", 10)),
  face=c(rep("C", 5), rep("D", 5), rep("E", 5), rep("F", 5))
)

p <- ggplot(d, aes(x=x, y=y)) +
     geom_col(orientation="x") +
     scale_y_reverse() +
     facet_wrap(group~.,
                scales="free_y",
                strip.position="right",
                nrow=2
                ) +
     coord_flip()
pg <- p +
  scale_y_break(c(7, 17), scales="free") +
  scale_y_break(c(19, 21), scales="free")
print(pg)
```



### Feature 10: Compatible with legends

Legends are automatically handled and preserved. You can position the legend anywhere using `theme(legend.position = ...)`. In this example, we move the legend to the bottom of the plot.

```{r message=FALSE}
pg <- pg + aes(fill=group) + theme(legend.position = "bottom")
print(pg)
```

### Feature 11: Supports all plot labels

All standard plot labels, including title, subtitle, caption, and tag, are supported and correctly placed around the broken plot. Standard theme elements for these labels (like font size, face, and position) are also respected.

```{r message=FALSE}
pg + labs(title="test title", subtitle="test subtitle", tag="A tag", caption="A caption") +
     theme_bw() +
     theme(
           legend.position = "bottom",
           strip.placement = "outside",
           axis.title.x=element_text(size=10),
           plot.title = element_text(size = 22),
           plot.subtitle = element_text(size = 16),
           plot.tag = element_text(size = 10),
           plot.title.position = "plot",
           plot.tag.position = "topright",
           plot.caption = element_text(face="bold.italic"),

     )
```

### Feature 12: Allows setting tick labels for subplots

Sometimes you might want specific control over the tick labels in each subplot segment. The `ticklabels` argument allows you to manually specify which labels should appear in each broken segment, overriding the default breaks.

```{r message=FALSE, fig.width=10, fig.height=6}
require(ggplot2)
library(ggbreak)
set.seed(2019-01-19)
d <- data.frame(
  x = 1:20,
  y =  c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22),
  group = c(rep("A", 10), rep("B", 10))
)

p <- ggplot(d, aes(x=x, y=y)) +
     scale_y_reverse() +
     scale_x_reverse() +
     geom_col(aes(fill=group)) +
     scale_fill_manual(values=c("#00AED7", "#009E73")) +
     facet_wrap(
         group~.,
         scales="free_y",
         strip.position="right",
         nrow=2
     ) +
     coord_flip()                                                                                                                                                                                                  

p +
     scale_y_break(c(7, 10), scales=0.5, ticklabels=c(10, 11.5, 13)) +
     scale_y_break(c(13, 17), scales=0.5, ticklabels=c(17, 18, 19)) +
     scale_y_break(c(19,21), scales=1, ticklabels=c(21, 22, 23))
```

### Feature 13: Compatible with dual axis

`ggbreak` works correctly with `scale_y_continuous(sec.axis = ...)` to create dual y-axes (e.g., metric vs imperial units). The secondary axis is broken in sync with the primary axis.

```{r fig.width=10, fig.height=5}
p <- ggplot(mpg, aes(displ, hwy)) +
     geom_point() +
     scale_y_continuous(
       "mpg (US)",
       sec.axis = sec_axis(~ . * 1.20, name = "mpg (UK)")
     ) +
      theme(
        axis.title.y.left = element_text(color="deepskyblue"),
        axis.title.y.right = element_text(color = "orange")
      )
p1 <- p + scale_y_break(breaks = c(20, 30))
p2 <- p + scale_x_break(breaks = c(3, 4))
p1 + p2
```

### Feature 14: Compatible with patchwork

`ggbreak` objects are fully compatible with `patchwork`. This means you can combine multiple broken plots, or combine broken plots with standard ggplot objects, into a single composite figure using simple arithmetic operators like `+` or `/`.

```{r message=FALSE, fig.width=8, fig.height=5, fig.keep="last"}
library(patchwork)

set.seed(2019-01-19)
d <- data.frame(
               x = 1:20,
               y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22)
)

p <- ggplot(d, aes(x, y)) + geom_col()
x <- p+scale_y_break(c(7, 17 ))

x + p
```

## Wrap plot

The `scale_wrap()` function wraps a 'gg' plot over multiple rows to make plots with long x-axes easier to read.

```{r fig.width=6, fig.height=8}
p <- ggplot(economics, aes(x=date, y = unemploy, colour = uempmed)) +
  geom_line()

p + scale_wrap(n=4)
```


Both categorical and numerical variables are supported.

```{r fig.width=7, fig.height=6}
ggplot(mpg, aes(class, hwy)) + 
  geom_boxplot() +
      scale_wrap(n = 2)
```

## Cut plot

The `scale_x_cut` or `scale_y_cut` cuts a 'gg' plot to several slices with the ability to specify which subplots to zoom in or zoom out.

```{r}
library(ggplot2)
library(ggbreak)
set.seed(2019-01-19)
d <- data.frame(
     x = 1:20,
     y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22)
 )
p <- ggplot(d, aes(x, y)) + geom_col()
p + scale_y_cut(breaks=c(7, 18), which=c(1, 3), scales=c(3, 0.5))
```

## Adjust the amount of space between subplots

The `space` parameter in `scale_x_break()`, `scale_y_break()`, `scale_x_cut()` and `scale_y_cut()` allows user to control the space between subplots.


```{r}
p + scale_y_cut(breaks=c(7, 18), which=c(1, 3), scales=c(3, 0.5), space=.5)
```

## Place legend at any position


```{r legend-subview, fig.keep="last"}
## original plot
p1 <- ggplot(mpg, aes(displ, hwy, color=factor(cyl))) + geom_point()

## ggbreak plot without legend
p2 <- p1 + scale_x_break(c(3, 4)) +
    theme(legend.position="none") 

## extract legend from original plot
leg = cowplot::get_legend(p1)

## redraw the figure
p3 <- ggplotify::as.ggplot(print(p2))

## place the legend 
p3 + ggimage::geom_subview(x=.9, y=.8, subview=leg)
```



## Note

The features we introduced for `scale_x_break` and `scale_y_break` also work for `scale_wrap`, `scale_x_cut` and `scale_y_cut`.

## FAQ

1. Incompatible with functions that arrange multiple plots

You can use `aplot::plot_list()` to arrange `ggbreak` objects with other `ggplot` objects. For other functions, such as `cowplot::plot_grid()` and `gridExtra::grid.arrange()`, you need to explictly call `print()` to `ggbreak` object, see also <https://github.com/YuLab-SMU/ggbreak/issues/36>.

Using `print()` is a secret magic to make `ggbreak` compatible with other packages, including [export](https://github.com/YuLab-SMU/ggbreak/issues/37). 

