Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to create custom pch shapes in R?

Many plotting functions in R use the graphical parameter pch to specify the shape of the data points. According to R documentation, there are 26 vector shapes to pick between, as well as the option to use ASCII characters.

Would it be possible to specify other simple vector shapes to use in plots, such as the examples below? enter image description here

There are some answers on stack overflow for creating plots with images as the points, but I feel this is a distinct question, as I would like to create vector shapes compatible with other graphical parameters such as col and cex. Another answer suggests using unicode symbols which is my best option so far, but it's still difficult to find the precise symbols I want.

Surely R has somewhere stored the code that creates the 26 vector shapes that are available, and it would be possible to write code in the same format for new shapes that could be used in the same way? A pointer in the right direction (or confirmation that this is totally impossible) would be great.

like image 493
J. Pennycook Avatar asked Sep 10 '25 14:09

J. Pennycook


1 Answers

You can write a function that draws arbitrary shapes as a scatter plot. It functions in the same way as the base R graphics function points, except it can take a custom shape as an argument:

points_custom <- function(x, y, shape, col = 'black', cex = 1, ...) {
  
  if(missing(shape)) {
    points(x, y, col = col, cex = cex, ...) 
  } 
  else {
    shape <- lapply(shape, function(z) z * cex)
    Map(function(x_i, y_i) {
    a <- grconvertX(grconvertX(x_i, 'user', 'inches') + shape$x, 'inches', 'user')
    b <- grconvertY(grconvertY(y_i, 'user', 'inches') + shape$y, 'inches', 'user')
    polygon(a, b, col = col, border = col, ...)
    }, x_i = x, y_i = y)
  }
  invisible(NULL)
}

If we create some test data, we will see that the default behaviour is the same as points:

set.seed(1)

x_vals <- 1:10
y_vals <- sample(10)

plot(1:10, , type = 'n')
points_custom(x_vals, y_vals)

enter image description here

The difference is that we can pass arbitrary shapes to be used to draw the points. These shapes should take the form of an x, y list of co-ordinates of the vertices of your shape. These will be used to draw polygons. For example, your 'propeller' shape on the left would be approximated by the following co-ordinates:

my_shape1 <- list(x = c(-0.01, 0.01, 0.01, 0.0916, 0.0816, 
                        0, -0.0816, -0.0916, -0.01), 
                  y = c(0.1, 0.1, 0.01, -0.0413, -0.0587, 
                        -0.01, -0.0587, -0.0413, 0.01))

And your 'angled Z' shape on the right by these co-ordinates:

my_shape2 <- list(x = c(0.007, 0.007, 0.064, 0.078, -0.007, 
                        -0.007, -0.064, -0.078), 
                  y = c(0.078, -0.042, 0.007, -0.007, -0.078, 
                        0.042, -0.007, 0.007))

These shapes can be passed to the points_custom function like so:

plot(1:10, , type = 'n')
points_custom(x_vals, y_vals, my_shape1)

enter image description here

plot(1:10, , type = 'n')
points_custom(x_vals, y_vals, my_shape2)

enter image description here

And we can apply the usual cex and col arguments:

plot(1:10, , type = 'n')
points_custom(x_vals, y_vals, my_shape1, col = 'red', cex = 2)

enter image description here

like image 83
Allan Cameron Avatar answered Sep 13 '25 03:09

Allan Cameron