grantmcdermott · GitHub

@grantmcdermott

Copy link Copy Markdown

Owner

Closes #236.

Adds a new draw argument that allows users to pass drawing functions that are simply captured and evaluated directly.

pkgload::load_all(("~/Documents/Projects/tinyplot"))
#> ℹ Loading tinyplot
library(marginaleffects)
fit = lm(Sepal.Length ~ (Sepal.Width + Petal.Length + Petal.Width) * Species, iris)
mfx = avg_slopes(fit, variables = c("Sepal.Width", "Petal.Length", "Petal.Width"), by = "Species")
with(
  mfx,
  plt(
    x = term, y = estimate, ymin = conf.low, ymax = conf.high,
    type = "errorbar", pch = 19,
    facet = Species, facet.args = list(bg = "gray90"), frame = FALSE,
    draw = abline(h = 0, lty = 2, col = "hotpink")
  )
)

One nice feature is that you can leverage the internal facet looping counter (namely: ii) to construct programmatic drawings.

plt(
  mpg ~ wt, facet = ~am, data = mtcars,
  draw = text(5, 30, paste("Facet", ii), adj = 1, col = "red", font = 2)
)

@grantmcdermott

@vincentarelbundock

Copy link Copy Markdown

Collaborator

Do we need to expose this to users?

We could have a type_abline() that creates this function internally and passes it to the facet drawing code. That would be consistent with our current user interface.

Then, type_hline(), type_vline(), etc. are all sensible shortcuts.

@grantmcdermott

@grantmcdermott

Copy link Copy Markdown

Owner Author

Do we need to expose this to users?

We could have a type_abline() that creates this function internally and passes it to the facet drawing code. That would be consistent with our current user interface.

Then, type_hline(), type_vline(), etc. are all sensible shortcuts.

Hmmm, I have a feeling you're going to warn me against argument proliferation... but I don't necessarily see this as an either-or situation. I think we could add type_abline() & co. without necessarily duplicating the role (or convenience) of draw.

For example, sometimes I just want to add text annotation to a plot and this requires a different logic to passing labels through a data frame (which I imagine is what type_text() will leverage once we support it.) Plus you can do kind of fun (dumb) things like this:

pkgload::load_all(("~/Documents/Projects/tinyplot"))
#> ℹ Loading tinyplot
plt(
  Temp ~ Day, facet = ~Month, data = airquality,
  draw = {
    abline(h = 80, lty = 2)
    text(5, 80, "Hot", adj = 1, col = "red", pos = 3)
    text(5, 80, "Cold", adj = 1, col = "blue", pos = 1)
  }
)

Created on 2024-11-07 with reprex v2.1.1

@vincentarelbundock

Copy link Copy Markdown

Collaborator

Hmmm, I have a feeling you're going to warn me against argument proliferation...

lol, yeah. I have a reputation now ;-)

I will say that, in this case, I can see the motivation. What is cool about draw is that it is totally generic, so it makes sense as an argument to the top level function. And your examples suggest that this could open up applications that we haven't thought about yet, so it might be a good investment for the future.

@grantmcdermott

Copy link Copy Markdown

Owner Author

What is cool about draw is that it is totally generic, so it makes sense as an argument to the top level function. And your examples suggest that this could open up applications that we haven't thought about yet, so it might be a good investment for the future.

Yeah, exactly. I think that this could open up a bunch of cool features and flexibility. Two quick additional examples to underscore the point:

  1. Passing draw = legend to create custom annotations.
tpar(pch = 19)
plt(
  mpg ~ wt, data = mtcars, facet = ~am, legend = FALSE, #axes = "l",
  draw = legend("topright", legend = as.roman(ii), bg = "lightcyan", x.intersp = 0)
)

  1. Passing plt back to itself (very "Inception"-esque) to plot the full dataset in the background of a faceted plot. You can also do this for spagetti (line) plots to highlight individual series.
data("penguins", package = "palmerpenguins")
plt(
  bill_length_mm ~ body_mass_g | species, data = penguins, facet = "by", legend = FALSE, axes = "l",
  draw = plt(bill_length_mm ~ body_mass_g , data = penguins, add = TRUE, col = "lightgray")
)

I'll add a few more examples to to the docs and then we can review again.

Question: One potential downside is that the draw elements are always drawn in the background, before the main plot elements. But I can imagine times where you'd want to draw on top of the main plot elements. Should we add a companion argument to draw that does this (maybe ink)?

@vincentarelbundock

Copy link Copy Markdown

Collaborator

Question: One potential downside is that the draw elements are always drawn in the background, before the main plot elements. But I can imagine times where you'd want to draw on top of the main plot elements. Should we add a companion argument to draw that does this (maybe ink)?

What happens if they use draw in our future tinyplot_add() function? To me, that would feel like the more natural way to implement this.

@zeileis

Copy link Copy Markdown

Collaborator

The penguins example is cool, very nice!

@grantmcdermott

Copy link Copy Markdown

Owner Author

Minor update. I'm going to wait for #246 to be merged first, since I'll build on some changes/docs forthcoming that PR.

@grantmcdermott

@grantmcdermott

Read the original on github.com ↗