Skip to content

Shapes provided via strings instead of integers #2338

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

Merged
merged 27 commits into from
May 10, 2018
Merged

Shapes provided via strings instead of integers #2338

merged 27 commits into from
May 10, 2018

Conversation

daniel-barnett
Copy link
Contributor

Implementation of #2075

data(mtcars)
n_obs <- nrow(mtcars)
pch_table <- c("0" = "square open",
               "1" = "circle open",
               "2" = "triangle open",
               "3" = "plus",
               "4" = "cross",
               "5" = "diamond open",
               "6" = "triangle open down",
               "7" = "square cross",
               "8" = "asterisk",
               "9" = "diamond plus",
               "10" = "circle plus",
               "11" = "star",
               "12" = "square plus",
               "13" = "circle cross",
               "14" = "square triangle",
               "15" = "square",
               "16" = "circle small",
               "17" = "triangle",
               "18" = "diamond",
               "19" = "circle",
               "20" = "bullet",
               "21" = "circle filled",
               "22" = "square filled",
               "23" = "diamond filled",
               "24" = "triangle filled",
               "25" = "triangle filled down")

## Testing previous functionality remains

# All defaults remain the same
ggplot(mtcars) + geom_point(aes(hp, cyl))

# Numeric shapes remain the same
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = 1, size = 3)

# Single character shapes remain the same
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = "a", size = 3)

# Single character shapes for all observations remain the same
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = c(letters, LETTERS)[1:n_obs], size = 3)

# Manually setting shape
mtcars$am2 <- ifelse(mtcars$am, "Manual", "Automatic")
ggplot(mtcars) + geom_point(aes(hp, cyl, shape = am2)) + scale_shape_manual(values = 2:3)

# Manually setting shapes with names
ggplot(mtcars) + geom_point(aes(hp, cyl, shape = am2)) + 
  scale_shape_manual(values = c("Manual" = 2, "Automatic" = 3)) 

## Testing new functionality

# Constant shape defined by a string
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = "triangle", size = 3)

image

# Constant shape defined by a unique substring
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = "pl", size = 3)

image

# Constant shape string defined for all observations
shape_vect <- c(unname(pch_table), unname(pch_table)[1:(n_obs - length(pch_table))])
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = shape_vect, size = 3)

image

# Manually setting shape with strings
mtcars$am2 <- ifelse(mtcars$am, "Manual", "Automatic")
ggplot(mtcars) + geom_point(aes(hp, cyl, shape = am2)) + 
  scale_shape_manual(values = c("triangle open", "plus"))

image

# Manually setting shapes with strings and named vectors
ggplot(mtcars) + geom_point(aes(hp, cyl, shape = am2)) + 
  scale_shape_manual(values = c("Manual" = "triangle open", "Automatic" = "plus")) 

image

# ERROR: Wrong number of shapes defined 
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = c("triangle", "circle"), size = 3)

# ERROR: Constant shape defined by a non-unique substring
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = "tri", size = 3)

# ERROR: No such shape string 
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = "void", size = 3)

# ERROR: Wrong string within a set
shape_vect[10] <- "void"
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = shape_vect, size = 3)

# ERROR: Multiple wrong strings within a set
shape_vect[11] <- "another_void"
shape_vect[21] <- "yetanother_void"
ggplot(mtcars) + geom_point(aes(hp, cyl), shape = shape_vect, size = 3)

@hadley
Copy link
Member

hadley commented Nov 30, 2017

Could you please add a couple of unit tests for translate_shape()?

We also need to consider where this behaviour should be documented. Somewhere in geom_point()?

R/scale-manual.r Outdated
@@ -56,6 +56,12 @@ scale_size_manual <- function(..., values) {
#' @rdname scale_manual
#' @export
scale_shape_manual <- function(..., values) {
if (is.character(values) && nchar(values[1]) > 1) {
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be better to incorporate the existing behaviour for single letters in to translate_shape_string(). An easy way do that would be to combine pch_table with setNames(letters, letter)

R/geom-point.r Outdated
"18" = "diamond",
"19" = "circle",
"20" = "bullet",
"21" = "circle filled ",
Copy link
Member

Choose a reason for hiding this comment

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

Trailing space

R/geom-point.r Outdated
@@ -98,6 +98,14 @@ geom_point <- function(mapping = NULL, data = NULL,
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE) {
dots <- list(...)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is the right place to do translation - it would be better to do so during drawing.

@daniel-barnett
Copy link
Contributor Author

I'll get on those and update the pull request this weekend.

I think documentation in geom_point() is natural along with a comment saying that these would also work for other layers based on points.

@hadley
Copy link
Member

hadley commented May 1, 2018

Are you interesting in finishing this off? Sorry for dropping the ball on you, but there are a few little style tweaks needed.

No problem if you don't have time in the next week, just let me know and I'll do it myself (but I wanted you to have the opportunity to finally take this over the finish line if you wanted)

@daniel-barnett
Copy link
Contributor Author

Sure, let me know!

invalid_strings <- is.na(shape_match)
nonunique_strings <- shape_match == 0

if (any(invalid_strings)) {
Copy link
Member

Choose a reason for hiding this comment

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

Can you please follow the standard tidyverse style here? (Main problem is that ) should go on own line; you can use styler package to do automatically)

R/geom-point.r Outdated
bad_string <- unique(shape_string[invalid_strings])
collapsed_names <- paste0(bad_string, collapse = "', '")
stop(
"Invalid shape name: '", collapsed_names, "'",
Copy link
Member

Choose a reason for hiding this comment

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

Would you mind having a read of http://style.tidyverse.org/error-messages.html and seeing if you can rewrite these errors to more closely follow the guidelines?

Copy link
Contributor Author

@daniel-barnett daniel-barnett May 2, 2018

Choose a reason for hiding this comment

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

For the ambiguous shape names, I've just put in the number of matches that the string would give; I'm not sure, but it might be nicer to give something like the following?

'tri' matches: triangle, triangle open, and 3 others

Copy link
Member

Choose a reason for hiding this comment

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

That's probably a bit too much effort for this case

@@ -0,0 +1,81 @@
context("translate_shape_string")
Copy link
Member

Choose a reason for hiding this comment

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

These tests should go in tests/testthat/test-geom-point.R

context("translate_shape_string")

test_that("strings translate to their corresponding integers", {
shape_strings <- c(
Copy link
Member

Choose a reason for hiding this comment

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

There's no need to systematically test ever possible value - the main thing you want to achieve in this test is to make sure everything is plumbed together correctly.

})

test_that("non-unique substrings of shape names raise an error", {
shape_strings <- c(
Copy link
Member

Choose a reason for hiding this comment

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

Similarly you only need to test a single string here, and in the next error (and you could probably combine the two tests)

@daniel-barnett
Copy link
Contributor Author

Also, would it be best to document this in the Aesthetic Specifications vignette?

Copy link
Member

@hadley hadley left a comment

Choose a reason for hiding this comment

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

Mention in the aesthetic specification vignette would be great!

NEWS.md Outdated
@@ -177,6 +180,7 @@ up correct aspect ratio, and draws a graticule.
use matrix-columns. These are rarely used but are produced by `scale()`;
to continue use `scale()` you'll need to wrap it with `as.numeric()`,
e.g. `as.numeric(scale(x))`.
>>>>>>> upstream/master
Copy link
Member

Choose a reason for hiding this comment

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

You missed this

@@ -0,0 +1,32 @@
context("translate_shape_string")
Copy link
Member

Choose a reason for hiding this comment

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

And this should now become geom-point

)

expect_equal(translate_shape_string(shape_strings[1]), 0)
expect_equal(translate_shape_string(shape_strings), 0:2)
Copy link
Member

Choose a reason for hiding this comment

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

It's not immediately clear to me what the intent of these tests are — I think it's to check that translate_shape_string() is vectorised? If so, I think you should create a new test, and I think you should only need one expectation.

@hadley
Copy link
Member

hadley commented May 9, 2018

Looks good! Can you please take a look at the CI failures? You might just need to merge/rebase to get fixes from the master branch.

@daniel-barnett
Copy link
Contributor Author

All passing now (the perils of using newer base R functions!)

@hadley hadley merged commit 22691ab into tidyverse:master May 10, 2018
@hadley
Copy link
Member

hadley commented May 10, 2018

Thanks for all your hard work on this!

@lock
Copy link

lock bot commented Nov 6, 2018

This old issue has been automatically locked. If you believe you have found a related problem, please file a new issue (with reprex) and link to this issue. https://reprex.tidyverse.org/

@lock lock bot locked and limited conversation to collaborators Nov 6, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants