Skip to content

Conversation

@szhorvat
Copy link
Member

@szhorvat szhorvat commented Jul 7, 2025

This is for #1907

This needs more work, of course, but I very much hope that it could be part of the next release. Can you take it over @schochastics ? Even this one-liner change makes a big difference IMO.

It'd be even better if #1956 could be addressed at the same time.

CC @maelle


Examples

plot(make_ring(5))
image
plot(make_lattice(c(2,4)))
image
plot(make_graph(~ 1-2-3-1-4))
image

See some more example in igraph/igraph#2781


Further improvements I'd suggest

  • Expose the alignment feature to users as align_layout() (or choose a better name, but I recommend not starting with layout_ to avoid confusion with functions that create layouts)
  • Increase the iteration count for layout_with_fr() for small graphs only. It makes a notable improvement.
  • layout_nicely() should identify forests/trees #1957

Fix #1957
Fix #1907

@szhorvat
Copy link
Member Author

szhorvat commented Jul 7, 2025

Yes, this is expected.

It doesn't always do well with layouts that are rotationally symmetric, such as a cycle graph, a complete graph, or an $n\times n$ grid. An $n\times m, n\ne m$ grid would be fine.

What the system does is that it attempts to align, and if the order parameter is too small (it'd be zero with a perfectly symmetric layout), it removes an edge and tries again. With a $C_4$ or a grid like this, what may happen is that the first step succeeds due to imperfections of the output from layout_with_fr(), and there is no need for a second try after an edge removal. If it succeeds, sometimes it aligns the corners, i.e. gives a 45 degree orientation, which is still better than something that's neither 0/90, nor 45.

A way to improve on this is to increase the iteration count in layout_with_fr(), even by as much as a factor of 10. This is more computationally costly, but I think it's worth doing for very small graphs, where good layout matters the most anyway.

@schochastics
Copy link
Contributor

schochastics commented Jul 7, 2025

(Sorry I delete my comment because I got that this is intended)
@szhorvat please check

@szhorvat
Copy link
Member Author

szhorvat commented Jul 7, 2025

Once we have this in R, it'll be easier to experiment and we can get feedback from users. Then we can experiment with the threshold that controls when to remove an edge and try again.

One more comment here is that I believe this algorithm to be practically fast (it's linear time), but alignment is somewhat pointless for very large graphs. So it's okay to be safe and skip doing it for very large ones (e.g. above 1000 vertices).

@szhorvat
Copy link
Member Author

szhorvat commented Jul 7, 2025

Here's an example with $C_4$. Sometimes it's at 0 degrees, sometimes at 45, and a few times it completely fails. After increasing the iteration count, it still does both 0 and 45, but it is much less likely to fail completely.

image

@szhorvat
Copy link
Member Author

szhorvat commented Jul 7, 2025

Brainstorming a bit about the best interface:

How about adding an align parameter to all organic layouts, and setting it to TRUE by default? Or even just auto-aligning all organic layouts above a certain vertex count, without an option to disable this behaviour?

This is perhaps nicer than restricting this feature to layout_nicely()? It also resolves the issue with deciding what layouts to align (e.g. that trees don't need alignment).

@schochastics
Copy link
Contributor

My opinion:
Leave it at layout_nicely() for now but expose align_layout() which this PR does.
Adding this tacitly to all layout functions is not a good idea because it removes the non-determinism which might confuse users. In the longrun, we probably should add an parameter (or an option?) that is either TRUE or FALSE (for backward compatibility the better choice) as default.

But I do not fell that strongly about this

@schochastics schochastics added this to the 2.2.0 milestone Jul 8, 2025
@schochastics schochastics marked this pull request as ready for review July 9, 2025 09:24
@schochastics schochastics changed the title feat: layout_nicely() now aligns the layout with the axes feat: expose align_layout() and add to layout_nicely() to align layout with axis automatically Jul 10, 2025
@schochastics schochastics merged commit 661cb38 into main Jul 10, 2025
6 checks passed
@schochastics schochastics deleted the feat/align-layout branch July 10, 2025 18:10
schochastics added a commit that referenced this pull request Aug 25, 2025
…ayout with axis automatically (#1958)

Co-authored-by: schochastics <david@schochastics.net>
krlmlr added a commit that referenced this pull request Oct 13, 2025
igraph 2.2.0

Update C core to version 0.10.17. See <https://github.com/igraph/rigraph/blob/20552ef94aed6ae4b23465ae8c7e4d3b0e558c71/src/vendor/cigraph/CHANGELOG.md> for a complete changelog, in particular the section "Breaking changes".

- Generate almost all R implementations (#2047).

- Expose `align_layout()` and add to `layout_nicely()` to align layout with axis automatically (#1907, #1957, #1958).

- Expose `simple_cycles()` which lists all simple cycles (#1573, #1580).

- Expose `is_complete()`, `is_clique()` and `is_ivs()` (#1316, #1388, #1581).

- Expose `find_cycle()` (#1471, #1571).

- Expose `feedback_vertex_set()` to find a minimum feedback vertex set in a graph (#1446, #1447, #1560).

- Add `weights` parameter to `local_scan()` (#1082, #1448, #1982).

- Add more layouts to `tkplot()` (#160, #1967).

- Add `plot(mark.lwd = )` to change line width of mark.groups (#306, #1898).

- Add `plot(vertex.label.angle = , vertex.label.adj = )` arguments to rotate vertex labels (#106, #1899).

- Add relative size scaling to vertices in `plot()` (@gvegayon, #172).

- Split `sample_bipartite()` into two functions for the G(n, m) and G(n, p) case (#630, #1692).

- Implement multi attribute assignment (#55, #1916) and adding attributes via data frames (#1373, #1669, #1716). Support factors in `graph_from_data_frame()` (#34, #1829).

- All `_hrg()` functions check their argument (#1074, #1699).

- HRG printing with `type = "auto"` uses `"plain"` for large trees (#1879).

- `get_edge_ids()` accepts data frames and matrices (#1663).

- `igraph_version()` returns version of C core in an attribute (#1208, #1781).

- Breaking change: change arguments default and order for `graph_from_lcf()` (#1858, #1872).

- Breaking change: Subset assignment of a graph avoids addition of double edges and ignores loops unless the new `loops` argument is set to `TRUE` (#1662, #1661).

- Breaking change: remove deprecated `neimode` parameter from `bfs()` and `dfs()` (#1105, #1526).

- Breaking change: stricter deprecation of non-functional parameters of `layout_with_kk()` and `layout_with_fr()` (#1108, #1628).

- `NA` attribute values are replaced with default values in `plot()` (#293, #1707).

- `NA` checking only in from/to columns of edge data frame (#1906).

- Keep vertex attribute type for `disjoint_union()` (#1640, #1909).

- Error in bipartite projection if `type` is not a vertex attribute (#898, #1889).

- Do not try to destroy non-initialized SIR objects upon error (#1888).

- Added proper `NA` handling for matrix inputs (#917, #918, #1828).

- Remove string matrix support from functions operating on biadjacency matrices (#1540, #1542, #1803).

- Integer vectors are validated before transferring them to the C library (#1434, #1582).

- Changed base location for `graph_from_graphdb()` and added tests (#1712, #1732).

- Recycling of logical vectors when indexing into edge/vertex selectors now throws an error (#848, #1731).

- Use `function()` instead of `(x)` in `arrow.mode` (#1722).

- Temporarily disable generating an interface for `igraph_simple_cycles_callback()` as the framework for handling callback functions is not yet present.

- Adjust loop position to vertex size in `plot()` (#1980).

- Don't rescale plot coordinates to `[-1,1] x [-1,1]` by default (#1492, #1956, #1962).

- Fail if `"layout"` attribute doesn't match the number of vertices (#1880).

- Automatically arrange loops in `plot()` (#407, #556, #1881).

- Vectorized drawing of arrows in `plot()` (#257, #1904).

- Allow more than one edge label font family in `plot()` (#37, #1896).

- Pie shapes now work as intended (#1882, #1883).

- Loops not plotted on canvas (#1799, #1800).

- Replace `NA` values in `label` attributes in `plot()` with default values (#1796, #1797).

- Removed duplicated plotting of arrow heads (#640, #1709).

- Correct mapping of edge label properties in plots when loops are present (#157, #1706).

- Welcome Maëlle Salmon and David Schoch as authors (#1733), add author links (#1821).

- Remove demos (#2008).

- Add 2023 preprint (#1240, #1984).

- Update allcontributors info (#1975).

- Link to replacements of deprecated functions (#1823).

- Add documentation of all file formats to `read_graph()` and `write_graph()` (#777, #1969). Recommend `saveRDS()` and `readRDS()` for saving and loading graphs (#1242, #1700).

- Document return value of `make_clusters()` (#1794).

- Clarify that `girth()` returns `Inf` for acyclic graphs (@eqmooring, #1831).

- Clarify the use of weights in `layout_with_kk()`.

- Refer to current latest version of R in troubleshooting page.

- Fix typos in `laplacian_matrix()` documentation.

- Document ellipsis in `cohesion()` (#971, #1985).

- Correct the description of the `weights` parameter of `hits_scores()`.

- Better describe output of `all_shortest_paths()` (#1029, #1778).

- `make_graph()` now supports `"Groetzsch"` as an alias of `"Grotzsch"`. This change was implemented in the C core.

- Update description of `order` parameter of `ego()` and related functions (#1746).

- Added lifecycle table (#1525).

- Add more about igraph.r2cdocs in the contributing guide (#1686, #1697).

- Accelerate check if an index sequence corresponds to the entire list of vertices (#1427, #1818).

- Faster single bracket querying of a graph (#1465, #1658).
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

Successfully merging this pull request may close these issues.

layout_nicely() should identify forests/trees Auto-align graph layouts with layout_nicely()

3 participants