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

"contour" #218

Open
Tracked by #97
grantmcdermott opened this issue Sep 16, 2024 · 5 comments
Open
Tracked by #97

"contour" #218

grantmcdermott opened this issue Sep 16, 2024 · 5 comments

Comments

@grantmcdermott
Copy link
Owner

No description provided.

@grantmcdermott
Copy link
Owner Author

We could use graphics::contourLines to calculate the appropriate breaks, followed by some internal data reconstruction to grab the group IDs.

Quick proof of concept that also uses that 'nested' ID coloring approach mentioned in #41 (comment) (contour plots are another case where this nesting would be necessary).

library(tinyplot)

cl = contourLines(z = volcano)
cl = lapply(cl, as.data.frame)
cl = do.call(rbind, unname(Map(cbind, id = seq_along(cl), cl)))
head(cl)
#>   id level         x         y
#> 1  1   100 1.0000000 0.5515289
#> 2  1   100 0.9883721 0.5515289
#> 3  1   100 0.9778109 0.5666667
#> 4  1   100 0.9767442 0.5681956
#> 5  1   100 0.9651163 0.5681956
#> 6  1   100 0.9545551 0.5833333

cols = with(cl, tapply(factor(level), id, FUN = `[[`, 1))  # grab group colours
plt(
    y ~ x | id, cl,
    type = "l",
    col = hcl.colors(length(unique(cols)))[cols],
    legend = FALSE
)
#> Warning in tinyplot.default(x = x, y = y, by = by, facet = facet, facet.args = facet.args, : 
#> Continuous legends not supported for this plot type. Reverting to discrete legend.

Filled contour plots would take a bit more thinking; the below doesn't work properly because of sub-optimal joins of the 'outer' polygons that should really extend into the plot margin. Also: each polygon should be drawn in 'donut' fashion—with the hole determined by the next contour—rather than filling in all the interior space.

plt(
    y ~ x | id, cl,
    type = "polypath", #rule = 'evenodd',
    col = hcl.colors(length(unique(cols)))[cols],
    legend = FALSE, fill = 0.1
)
#> Warning in tinyplot.default(x = x, y = y, by = by, facet = facet, facet.args = facet.args, : 
#> Continuous legends not supported for this plot type. Reverting to discrete legend.

Created on 2024-09-16 with reprex v2.1.1

@zeileis
Copy link
Collaborator

zeileis commented Sep 17, 2024

Good idea. I'm not fully clear though whether you already expect to have some sort of three-dimensional data already (x and y coordinates plus height/levels) or whether it should be two-dimensional.

The latter would conceptually be straightforward but necessitate (a) new type(s) of plot, e.g., "density2d" and/or "hexbin" or so. See graphics::smoothScatter and hexbin::hexbin or various 2D density variations with ggplot2 on r-charts and the r-graph-gallery, respectively. For all of these the basic formula would just be tinyplot(y ~ x, ...) which could then be combined with faceting as usual. It would necessitate some function for computing the densities or hexbins in the background, though.

For three-dimensional data like the volcano data from your illustration, it is not so clear to me what user interface you are aiming for. Which of the transformations you do in your code would the user have done before calling tinyplot, which would be done internally by the plot type?

@grantmcdermott
Copy link
Owner Author

All valid points! I haven't had time to think about it much, just wanted to jot down a quick proof of concept. You're absolutely right that the implementation would require some thought w.r.t. user interface. (Doesn't lattice have a contour method that might give us some ideas for potential formula logic?)

@zeileis
Copy link
Collaborator

zeileis commented Sep 17, 2024

Good point. In lattice these are called levelplot() (for heatmaps) and contourplot() (for contour lines, with or without filling). Both of them are designed for 3D data and expect formulas like z ~ x + y. A third function with the same kind of interface is wireframe() which is the lattice version of persp().

For the volcano data all three functions can be used like this on a matrix/table:

levelplot(volcano)

Or you can set up a data frame with three variables like this:

volcano2 <- data.frame(
  row = as.vector(row(volcano)),
  column = as.vector(col(volcano)),
  height = as.vector(volcano)
)

And then you can use the formula interface:

levelplot(height ~ row + column, data = volcano2)

If you additionally set aspect = "iso" you also get the same aspect ratio as in the matrix interface.

In short, I guess this is closer to what you had in mind, right? The plots I was looking for (smoothed scatterplots for 2d data) could then possibly be accomplished by deriving the density estimate and then calling the 3d functionality.

@zeileis
Copy link
Collaborator

zeileis commented Oct 14, 2024

Just a link so that I don't forget. It's not about contours but about heatmaps which we also mentioned above. Maybe this is useful for inspiration (maybe not): https://jbengler.github.io/tidyheatmaps/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants