thomasp85 / ggraph

Grammar of Graph Graphics

Home Page:https://ggraph.data-imaginist.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Providing custom bezier control points

krassowski opened this issue · comments

The documentation suggests that geom_edge_*2 functions allow customisation of the control points:

The version postfixed with a "2" uses the "long" edge format (see get_edges()) and makes it possible to interpolate node parameter between the start and end node along the edge.

It wasted a bit too much of my time to understand that in fact it does not allow to specify the control points manually but barely computes them arbitrarily. The documentation is of course technically right - this is what interpolation means (kind of because technically bezier curve is an interpolation too), but this was quite surprising.

So currently get_edges() always returns 2 rows per edge, white StatEdgeArc2 and remove_loop2 assume that two rows are present, in particular by selecting every other row as from/to vertices and using them to compute the arcs:

ggraph/R/geom_edge_arc.R

Lines 176 to 178 in febab71

data2 <- data[c(FALSE, TRUE), ]
data <- data[c(TRUE, FALSE), ]
create_arc(data, data2, params)

If I wanted to have the full control I could switch to ggforce::geom_bezier2 but this means loosing out on all the goodies that GeomEdgePath provides. I wish I could just specify stat=ggforce::StatBezier2 but it seems unsupported in geom_edge_*2.

Any thoughts about this?

For anyone interested, geom_edge_manual_arc defined as below works well, but it's a lot of boilerplate for everyday use!

geom_edge_manual_arc <- function(
    mapping = NULL, data = get_edges('long'),  # you probably do not want get_edges here (pass your data with control points instead)
    position = 'identity', arrow = NULL,
    n = 100, fold = FALSE, lineend = 'butt',
    linejoin = 'round', linemitre = 1,
    label_colour = 'black', label_alpha = 1,
    label_parse = FALSE, check_overlap = FALSE,
    angle_calc = 'rot', force_flip = TRUE,
    label_dodge = NULL, label_push = NULL,
    show.legend = NA, ...
) {
  mapping <-  ggraph:::complete_edge_aes(mapping)
  mapping <- ggraph:::aes_intersect(mapping, aes(
    x = x, y = y, group = edge.id,
    circular = circular
  ))
  layer(
    data = data, mapping = mapping, stat = ggforce::StatBezier2,
    geom = ggraph::GeomEdgePath, position = position, show.legend = show.legend,
    inherit.aes = FALSE,
    params = ggraph:::expand_edge_aes(
      list(
        arrow = arrow, lineend = lineend, linejoin = linejoin,
        linemitre = linemitre, na.rm = FALSE, n = n,
        interpolate = TRUE, fold = fold,
        label_colour = label_colour, label_alpha = label_alpha,
        label_parse = label_parse, check_overlap = check_overlap,
        angle_calc = angle_calc, force_flip = force_flip,
        label_dodge = label_dodge, label_push = label_push, ...
      )
    )
  )
}

yeah, you'd need a whole new edge geom for this. Arc was never intended as a general bezier geom for edges (in the same way that diagonal is a "fixed" bezier geom)

Such a geom would be quite niche so I'm afraid I can't prioritise that at the moment