igraph / rigraph

igraph R package
https://r.igraph.org
557 stars 202 forks source link

plot rescales and normalizes graph even if it has vertex x and y attributes #1492

Open DOSull opened 2 months ago

DOSull commented 2 months ago

What happens, and what did you expect instead?

I expect the graph drawing to be based on the x and y attributes of the vertices. The documentation for layout_nicely says

The current implementation works like this:

  1. If the graph has a graph attribute called ‘layout’, then this is used. If this attribute is an R function, then it is called, with the graph and any other extra arguments.
  2. Otherwise, if the graph has vertex attributes called ‘x’ and ‘y’, then these are used as coordinates. If the graph has an additional ‘z’ vertex attribute, that is also used.

When I force xlim and ylim to the range of those attributes, I get what I would expect by default, based on the documentation. There's a post in the forum where I asked about this, and the rescale = TRUE suggestion was made.

I think the issue is with the line of code where xlim = c(-1, 1) ylim = c(-1, 1) is imposed.

To reproduce

library(igraph)
n <- 11
a <- seq(0, 2 * pi, length.out = n + 1)[-1]
x <- 5 * cos(a)
y <- 3 * sin(a)
L <- matrix(c(x, y), ncol = 2)

G <- make_full_graph(n) |> 
  set_vertex_attr("x", value = x) |>
  set_vertex_attr("y", value = y)

par(mfrow = c(1, 3))
plot(G, layout = layout_nicely(G), axes = TRUE)
plot(G, layout = L, rescale=FALSE, axes = TRUE)
plot(G, xlim = range(V(G)$x), ylim = range(V(G)$y), 
     rescale = FALSE, asp = 1, axes = TRUE)

Created on 2024-09-08 with [reprex v2.1.1](https://reprex.tidyverse.org/)

image To be clear, the third panel is what I expect.

System information

I don't think any of this is relevant, but for completeness:

> version
platform       aarch64-apple-darwin20      
arch           aarch64                     
os             darwin20                    
system         aarch64, darwin20           
status                                     
major          4                           
minor          3.3                         
year           2024                        
month          02                          
day            29                          
svn rev        86002                       
language       R                           
version.string R version 4.3.3 (2024-02-29)
nickname       Angel Food Cake

All packages updated today, and igraph at version 2.0.3

krlmlr commented 2 months ago

Thanks. I see, even without the asp argument:

options(conflicts.policy = list(warn = FALSE))
library(igraph)

n <- 11
a <- seq(0, 2 * pi, length.out = n + 1)[-1]
x <- 5 * cos(a)
y <- 3 * sin(a)
L <- matrix(c(x, y), ncol = 2)

G <- make_full_graph(n) |>
  set_vertex_attr("x", value = x) |>
  set_vertex_attr("y", value = y)

plot(G, layout = layout_nicely(G), axes = TRUE)

plot(G, layout = L, rescale = FALSE, axes = TRUE)

plot(G, xlim = range(V(G)$x), ylim = range(V(G)$y), rescale = FALSE, axes = TRUE)

Created on 2024-09-12 with reprex v2.1.0

I'm confused why the first two plots keep the aspect ratio, but the third plot doesn't. I agree that we could do xlim = NULL, ylim = NULL in the formals and use c(-1, 1) for rescale = FALSE and whatever is the best default for rescale = TRUE .

Tidyverse design guide re NULL defaults: https://design.tidyverse.org/defaults-short-and-sweet.html#null-default

DOSull commented 2 months ago

I am in the habit when plotting geographical data (which is eventually the aim here) to include asp = 1 in plot options as usually you want geographic coordinates to be treated as equal in the two dimensions, which is why it is in my code example. Perhaps plot.igraph defaulting to asp = 1 has something to do with this line in the plot.R code:

asp <- params("plot", "asp")

but that's way too deep in the weeds of how igraph handles parameters and their defaults for me to have any certainty about that suggestion. But, e.g., I get

> igraph:::i.parse.plot.params(G, list())("plot", "asp")
[1] 1

which suggests that the plot function is setting asp to a default value of 1.

But really someone who knows how these internal workings of igraphwork (which might be you @krlmlr!) and especially understands how they are intended to work should comment.

In any case, there's a whole chunk of the code (starting at line 100) which is similar in format to above.

To be clear, I am totally guessing here. I only happened on the igraph:::i.etc. line above via this fairly random code snippet so I am way above my pay grade and outside my comfort zone here.