Git Product home page Git Product logo

Comments (10)

thomasp85 avatar thomasp85 commented on August 9, 2024 1

This is one of the things I’ll be working on. You’re welcome to try with a PR

from ggraph.

schochastics avatar schochastics commented on August 9, 2024

@thomasp85 Is this high up on your list? I have fiddled a bit with this issue.
The function below allows to create a parallel line of an edge between midpoints of nodes.
the shift parameter controls the distance.

edge_shift <- function(x1,y1,x2,y2,shift){
  v <- c(x2 - x1,y2 - y1)
  v <- v/sqrt((v[1]^2 + v[2]^2))
  v_perp <- c( -v[2], v[1] )
  
  return(c(x1 + shift*vperp[1],y1 + shift*vperp[2],
           x2 + shift*vperp[1],y2 + shift*vperp[2]))
}

This is what it can produce
geom_edge_parallel

of course this is no proper geom yet (ggproto is still mostly a mystery to me...) but if this is low priority for you I'll try to get my hands dirty on this.

from ggraph.

thomasp85 avatar thomasp85 commented on August 9, 2024

I want something that keeps the edge distance constant when resizing I think

from ggraph.

schochastics avatar schochastics commented on August 9, 2024

All right maybe I use this as an excuse to learn ggproto and see how far I can get

from ggraph.

thomasp85 avatar thomasp85 commented on August 9, 2024

I’ll help you through it. Just start a PR early on and I can come with suggestions

from ggraph.

schochastics avatar schochastics commented on August 9, 2024

Ok I got something, but not yet PR worthy due to some major issue outlined further down.

# function to create parallel edges ----
create_parallels <- function(data,params) {
  data$origID <- 1:nrow(data)
  data$.id <- paste(pmin(data$from, data$to), pmax(data$from, data$to), sep = '-')
  data <- data %>% arrange(.id)
  medge <- data %>% 
    group_by(.id) %>% 
    dplyr::summarise(medge=n()) %>%
    ungroup() %>% 
    transmute(medge)
  
  medge <- medge$medge  
  medge_shift <- unlist(sapply(medge,dseq,spread=params$spread))
  data$shift <- medge_shift*ifelse(data$from<data$to,1,-1)
  shifted <- t(apply(data[,c("x","y","xend","yend","shift")],1,
                     function(x) edge_shift(x[1],x[2],x[3],x[4],x[5])))

  data[,c("x","y","xend","yend")] <- shifted 
  data <- data %>% arrange(origID)
  data[["shift"]] <- NULL
  data[["origID"]] <- NULL
  data[[".id"]] <- NULL
  data[["from"]] <- NULL
  data[["to"]] <- NULL
  data
}

dseq <- function(n,spread){
  seq(-(n-1)*spread/2,(n-1)*spread/2,by=spread)
}
edge_shift <- function(x1,y1,x2,y2,shift){
  
  v <- c(x2 - x1,y2 - y1)
  v <- v/sqrt((v[1]^2 + v[2]^2))
  v_perp <- c( -v[2], v[1] )
  
  return(c(x1 + shift*v_perp[1],y1 + shift*v_perp[2],
           x2 + shift*v_perp[1],y2 + shift*v_perp[2]))
  
}

#Stat Function ----
StatEdgeParallel <- ggproto('StatEdgeParallel', StatLink,
                        setup_data = function(data, params) {
                          if (any(names(data) == 'filter')) {
                            if (!is.logical(data$filter)) {
                              stop('filter must be logical')
                            }
                            data <- data[data$filter, names(data) != 'filter']
                            
                          }
                          data <- create_parallels(data,params)
                          StatLink$setup_data(data, params)
                        },
                        default_aes = aes(filter = TRUE),
                        extra_params = c("na.rm","n",'spread')
)


#geom ----
geom_edge_parallel <- function(mapping = NULL, data = get_edges(),
                           position = "identity", arrow = NULL, spread = 0.02,
                           n = 100,
                           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 <- complete_edge_aes(mapping)
  mapping <- aes_intersect(mapping, aes_(x=~x, y=~y, xend=~xend, yend=~yend,
                                        from=~from, to=~to))
  layer(data = data, mapping = mapping, stat = StatEdgeParallel,
        geom = GeomEdgePath, position = position, show.legend = show.legend,
        inherit.aes = FALSE,
        params = expand_edge_aes(
          list(arrow = arrow, lineend = lineend, linejoin = linejoin,
               linemitre = linemitre, na.rm = FALSE, spread = spread, n = n,
               interpolate = FALSE,
               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, ...)
        )
  )
}

The only real new thing is the create_parallels call in StatEdgeParallel. Otherwise it is pretty much the same as for geom_edge_link.

g <- graph.full(3)
g <- add.edges(g,c(1,2,2,3,2,1))
E(g)$col <- c("a","a","a","b","b","c")
ggraph(g,"stress")+geom_edge_parallel(aes(col=col),spread=0.01)+geom_node_point(size=7)
#> Warning: Ignoring unknown aesthetics: from, to

Pretty sure that removing the warning would be easy. I added "from" and "to" in aes_intersect because I needed it to find edges between the same nodes. Don't know how to remove the warning though. The spread parameter controls the distance between the edges and here is where the biggest issue is when all nodes have the same y coordinate (or x for that matter)

gr <- graph_from_data_frame(data.frame(
  from = c(1, 1, 1, 1, 1, 2, 2, 2,2,2,2),
  to = c(2, 2, 2, 2, 2, 1, 1, 1,3,3,3),
  class = sample(letters[1:3], 11, TRUE)
))
xy <- cbind(c(1,2,3),c(0,0,0))

ggraph(gr, 'manual',node.positions=data.frame(x=xy[,1],y=xy[,2])) +
  geom_edge_parallel(spread=0.002)+
  geom_node_point(size=7)
#> Warning: Ignoring unknown aesthetics: from, to

of course it is fixable with scale_y_continuous.

ggraph(gr, 'manual',node.positions=data.frame(x=xy[,1],y=xy[,2])) +
  geom_edge_parallel(spread=0.002)+
  geom_node_point(size=7)+scale_y_continuous(limits=c(-0.2,0.2))
#> Warning: Ignoring unknown aesthetics: from, to

But that can not be a desired feature. So I am wondering how one would go about to draw parallel lines in this case. I assume that requires some deeper dive into grid? simply adjusting coordinates seems to not work in this special case. This might also be related to your comment "I want something that keeps the edge distance constant when resizing" I assume

from ggraph.

thomasp85 avatar thomasp85 commented on August 9, 2024

yeah... this is what I meant with constant edge distances... This needs to be done in a new makeContent method when the size of the viewport is known

it is going to get hairy :-)

from ggraph.

schochastics avatar schochastics commented on August 9, 2024

lol and here I am thinking I accomplished something. Guess that means I need to dig deeper into grid.

from ggraph.

thomasp85 avatar thomasp85 commented on August 9, 2024

You've absolutely accomplished something. The main computations just need to be moved closer to draw time :-)

I'm fine with taking your code and moving it around, but you are also welcome to play with grid... Have a look at how path capping is achieved in cappedPath.R if you dare ;-)

from ggraph.

schochastics avatar schochastics commented on August 9, 2024

well thanks :). I am gonna look into it. If I fail in the next two days, I'll send a PR with what I currently have

from ggraph.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.