Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Axis guide for logarithmic ticks #5500

Merged
merged 19 commits into from
Nov 24, 2023
Merged

Conversation

teunbrand
Copy link
Collaborator

This PR aims to fix #5325, in particular through #5325 (comment).

Briefly, it adds a new axis guide, guide_axis_logticks() that draws ticks at log10-spaced positions.

In comparison to annotation_logticks():

  • Only base 10 ticks. None of these base 2 or base e ticks. Even ?annotation_logticks says: "These tick marks probably make sense only for base 10.", which I agree with.
  • Proper axis, meaning label placement works out naturally and don't have to set clip = "off" when outside. Of course, can do equivalent of annotation_logticks(outside = FALSE) by setting negative tick lengths.
  • Takes style from theme
  • Has access to scale, so no need to guess whether user has transformed through scale or coord. User only needs to intervene when manually transformed.

In comparison with guide_axis():

  • Exact same key is used for the labels. The ticks get their own 'shadow'-key.
  • Ticks all use axis.ticks.{position}.{aes} theme setting, so no tick uses the axis.minor.ticks.{position}.{aes} setting.

Why WIP?

  • Should we be able to control the style of the minor/5 ticks and smallest/1 ticks seperately from the major/10 ticks? If so, do we handle that in the guide or make new theme settings?
  • Should we mark annotation_logticks() as superseded?
  • Once/if POC: secondary axis functionality in guide_axis() #5410 gets merged, we may think about providing a custom breaks and/or labels argument.

A bunch of demonstrations; ticks are placed in non-transformed, original data ranges. The label placement shows that guide_axis_logticks() does not intervene with that part of the guide and takes these verbatim from the scale.

devtools::load_all("~/packages/ggplot2")
#> ℹ Loading ggplot2

# Tiny wrapper for demonstrating scale/guide interactions
demo <- function(limits, trans = "identity", guide = "axis_logticks", ...) {
  scales::demo_continuous(limits, trans = trans, guide = guide, ...)
}

demo(c(0.1, 100))
#> scale_x_continuous(trans = trans, guide = guide)

Of course, these ticks start to make more sense with a log transformation:

demo(c(0.1, 100), trans = "log10")
#> scale_x_continuous(trans = trans, guide = guide)

Tick lengths are set in the guide. If not a <unit> they are interpreted as relative to the theme's tick length setting.

demo(
  c(0.1, 100), trans = "log10",
  guide = guide_axis_logticks(long = 5, mid = 2, short = 1)
)
#> scale_x_continuous(trans = trans, guide = guide)

By default, the ticks are placed 1 order of magnitude outside the limits. To clip these to the actual limits rather than the expanded limits, you can set expanded = FALSE.

demo(
  c(0.1, 100), trans = "log10", expand = expansion(add = 0.5),
  guide = guide_axis_logticks(expanded = FALSE)
)
#> scale_x_continuous(trans = trans, guide = guide, expand = expansion(add = 0.5))

When a user has manually transformed their data, the scale doesn't know about this so the ticks would be off. To intervene, one can set prescale_base to calculate appropriate ticks.

demo(log10(c(0.1, 100)), guide = guide_axis_logticks(prescale_base = 10))
#> scale_x_continuous(trans = trans, guide = guide)

Because ticks are placed in original data-space, this works naturally with compound transformations, like the reverse log10 transform.

demo(c(0.1, 100), trans = c("log10", "reverse"))
#> scale_x_continuous(trans = trans, guide = guide)

When the data include 0 or negative numbers, the ticks are mirrored around 0.
The reason for this is as follows:

  1. It works out really naturally with pseudo-log (and alike) transformations.
  2. A true log transform cannot include 0 or negative numbers, so we're not harming these transformations by mirroring.
demo(c(-100, 100), trans = "pseudo_log")
#> scale_x_continuous(trans = trans, guide = guide)

Of course, some decision then has to be made where near 0 one should stop drawing ticks, as otherwise one would end up with an infinite amount of ticks. The default is 0.1 but the user can control this too.

demo(
  c(-100, 100), trans = "pseudo_log", 
  guide = guide_axis_logticks(negative_small = 1)
)
#> scale_x_continuous(trans = trans, guide = guide)

Just to show that I'm not cheating this mirroring by seeing if the scale name is "pseudo-log", we can do the same with custom transformations:

demo(c(-100, 100), trans = trans_new("", asinh, sinh))
#> scale_x_continuous(trans = trans, guide = guide)

Created on 2023-10-28 with reprex v2.0.2

@teunbrand teunbrand added this to the ggplot2 3.5.0 milestone Nov 14, 2023
Merge branch 'main' into axis_logticks

# Conflicts:
#	DESCRIPTION
#	NAMESPACE
#	_pkgdown.yml
#	man/ggplot2-ggproto.Rd
#	tests/testthat/test-guides.R
@teunbrand
Copy link
Collaborator Author

teunbrand commented Nov 22, 2023

Now has customisation options for short ticks. Long ticks inherit from axis.ticks, middle ticks inherit from axis.minor.ticks and smallest ticks are set in guide_axis_logticks(short_theme) and inherit from the minor ticks. We chose not to define a new theme element for the shortest ticks, as it is only relevant in this axis.

Still unsure if there wouldn't be a better argument name than short_theme.

In example below, you can see that the smallest ticks can be turned off on the x-axis, and the inheritance on the y-axis.

devtools::load_all("~/packages/ggplot2")
#> ℹ Loading ggplot2

ggplot(msleep, aes(bodywt, brainwt)) +
  geom_point(na.rm = TRUE) +
  scale_x_log10(guide = guide_axis_logticks(
    short = 2,
    short_theme = element_blank()
  )) +
  scale_y_log10(guide = guide_axis_logticks(
    short_theme = element_line(linewidth = 2)
  )) +
  theme(
    axis.minor.ticks.y.left = element_line(colour = "red")
  )

Created on 2023-11-22 with reprex v2.0.2

@teunbrand teunbrand changed the title WIP: Axis guide for logarithmic ticks Axis guide for logarithmic ticks Nov 22, 2023
Copy link
Member

@thomasp85 thomasp85 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@teunbrand
Copy link
Collaborator Author

Thanks for the review Thomas!

@teunbrand teunbrand merged commit bea9089 into tidyverse:main Nov 24, 2023
12 checks passed
@teunbrand teunbrand deleted the axis_logticks branch November 24, 2023 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add option to reverse annotation_logticks
2 participants