7

In base graphics we can easily add tick-marks at different sides of the plot by using axis and the side argument:

par(tck = 0.025)
plot(1:10)
axis(side = 3, labels = FALSE)
axis(side = 4, labels = FALSE)

How can I mirror x and y axis ticks on the top and right in ggplot2? There is annotation_logticks which can do this, but only seems to work well for logarithmic scales (as the name of the function applies). For linear scales, is there a similarly easy way?

Henrik
  • 65,555
  • 14
  • 143
  • 159
hatmatrix
  • 42,883
  • 45
  • 137
  • 231

3 Answers3

17

From ggplot2 2.2.0 you may use the sec.axis argument in scale_.

dup_axis is a shorthand for creating a secondary axis that is a duplication of the primary axis, effectively mirroring the primary axis.

ggplot(data = mpg, aes(x = displ, y = hwy)) + 
  geom_point() +
  scale_x_continuous(sec.axis = dup_axis()) +
  scale_y_continuous(sec.axis = dup_axis())

enter image description here

Henrik
  • 65,555
  • 14
  • 143
  • 159
  • Awesome answer, but unfortunately it doesn't work when you have discrete variables on the x axis. The argument `sec.axis` is not available in `scale_x_discrete`. – thiagoveloso May 09 '19 at 22:36
4

This mirrors the axis tick marks (using gtable functions), and puts the tick marks inside the plot panel.

EDIT 18 April 2016 axis.ticks.margin is deprecated. Use text margins instead.

EDIT 19 Mrch 2015: Better positioning of the tick marks

library(ggplot2) # v2.1.0
library(gtable)  # v0.2.0
library(grid)

# Get a plot
p = ggplot(data.frame(x = 1:10, y = 1:10), aes(x,y)) + 
   geom_point() +
   theme_bw() +
   theme(panel.grid = element_blank(),
          axis.ticks.length=unit(-0.25, "cm"),
          axis.text.x = element_text(margin = margin(t = .5, unit = "cm")),
          axis.text.y = element_text(margin = margin(r = .5, unit = "cm")))

# Convert the plot to a grob
gt <- ggplotGrob(p)

# Get the position of the panel in the layout
panel <-c(subset(gt$layout, name=="panel", se=t:r))

## For the bottom axis
# Get the row number of the bottom axis in the layout
rn <- which(gt$layout$name == "axis-b")

# Extract the axis (tick marks only)
axis.grob <- gt$grobs[[rn]]
axisb <- axis.grob$children[[2]]  # Two children - get the second
axisb  # Note: two grobs - tick marks and text

# Get the tick marks
xaxis = axisb$grobs[[1]]  # NOTE: tick marks first
xaxis$y = xaxis$y - unit(0.25, "cm")  # Position them inside the panel

# Add a new row to gt, and insert the revised xaxis grob into the new row.
gt <- gtable_add_rows(gt, unit(0, "lines"), panel$t-1)
gt <- gtable_add_grob(gt, xaxis, l = panel$l, t = panel$t, r = panel$r, name = "ticks")

## Repeat for the left axis
# Get the row number of the left axis in the layout
panel <-c(subset(gt$layout, name=="panel", se=t:r))
rn <- which(gt$layout$name == "axis-l")

# Extract the axis (tick marks and axis text)
axis.grob <- gt$grobs[[rn]]
axisl <- axis.grob$children[[2]]  # Two children - get the second
axisl  # Note: two grobs -  text and tick marks

# Get the tick marks
yaxis = axisl$grobs[[2]] # NOTE: tick marks second
yaxis$x = yaxis$x - unit(0.25, "cm") # Position them inside the panel

# Add a new column to gt, and insert the revised yaxis grob into the new column.
gt <- gtable_add_cols(gt, unit(0, "lines"), panel$r)
gt <- gtable_add_grob(gt, yaxis, t = panel$t, l = panel$r+1, name = "ticks")

# Turn clipping off
gt$layout[gt$layout$name == "ticks", ]$clip = "off"

# Draw it
grid.draw(gt)

enter image description here

Sandy Muspratt
  • 31,719
  • 12
  • 116
  • 122
0

This doesn't solve the discrete issue, but the secondary axis need not be a duplication of the primary axis. So on "side=4" I can put a transformation of the "side=2" axis.

See more here: https://ggplot2.tidyverse.org/reference/sec_axis.html

e.g., scale_y_continuous(sec.axis = sec_axis(~ . + 10))