R/igraph-utils.R
62d41418
 ### - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5712a4d7
 ### A layout function for a splicing graph represented as an igraph object.
62d41418
 ###
 
 ### Experimental. Not ready yet!
5712a4d7
 layout.sgraph <- function(graph)
62d41418
 {
     ## Compute the 'x' col.
     vertices <- get.data.frame(graph, what="vertices")
     nodes <- vertices$name
     nnodes <- length(nodes)
     if (!identical(nodes[c(1L, nnodes)], c("R", "L")))
         stop("first and last nodes are expected to be \"R\" and \"L\", ",
              "respectively")
     max_SSid <- graph$max_SSid
     if (is.null(max_SSid)) {
         nxslot <- nnodes
         xslot <- seq_len(nxslot) - 1L
     } else {
         nxslot <- as.integer(max_SSid) + 2L
         xslot <- c(0L, as.integer(nodes[-c(1L, nnodes)]), nxslot - 1L)
     }
     x <- xslot * 2.0/(nxslot-1L) - 1.0
 
     ## Compute the 'y' col.
     edges <- get.data.frame(graph, what="edges")
     nedges <- nrow(edges)
     nin <- tabulate(factor(edges$to, levels=nodes), nbins=nnodes)
     nout <- tabulate(factor(edges$from, levels=nodes), nbins=nnodes)
     ## A sanity check.
     if (nin[1L] != 0L || nout[nnodes] != 0L)
         stop("\"R\" or \"L\" nodes cannot have incoming or outgoing edges, ",
              "respectively")
     nin_cumsum <- cumsum(nin)
     nout_cumsum <- cumsum(nout)
     ## A sanity check.
     if (nin_cumsum[nnodes] != nedges || nout_cumsum[nnodes] != nedges)
         stop("The sum of all the incoming edges for all nodes should be ",
              "equal to the total nb of edges. Same for the outgoing edges.")
     ## Nb of edges passing by.
     nbypass <- cumsum(c(0L, nout[-nnodes]) - nin)
     set.seed(33L)
     yslot <- sapply(nbypass+1L, sample, 1L)
     y <- yslot * 2.0/(nbypass+2L) - 1.0
 
     ## Return the layout matrix.
     cbind(x=x, y=y)
 }
 
 
 ### - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
39ca8ad2
 ### .igraph.to.graphNEL2() - Replacement for igraph::igraph.to.graphNEL()
 ###
 ### igraph::igraph.to.graphNEL() works on a graph with more than 1 edge
 ### between the same 2 nodes: surprisingly it returns a graphNEL object that
 ### is plottable, seems to have the correct number of edges, and passes the
 ### validGraph() test. Nonetheless it's broken! In particular, the edge
 ### attributes (aka the "edge data" in graphNEL jargon, found in
 ### x@edgeData@data for graphNEL object 'x') propagated from the original
 ### igraph object are corrupted. The .igraph.to.graphNEL2() function below
 ### fixes that, and only that (i.e. it repairs x@edgeData@data), because this
 ### is all what we care about.
62d41418
 ###
 
39ca8ad2
 .find_old2new_mapping <- function(old, new)
b0b9f841
 {
39ca8ad2
     N <- length(old)
     if (length(new) != N)
         stop("'old' and 'new' must have the same length")
     oo1 <- order(old)
     oo2 <- order(new)
     ans <- integer(N)
     ans[oo2] <- oo1
     stopifnot(all(old[ans] == new))
     ans
 }
 
 .repair_edge_data <- function(edge_data, graph)
 {
     old_edges <- get.data.frame(graph)
     stopifnot(identical(colnames(old_edges)[1:2], c("from", "to")))
     old_edge_names <- paste0(old_edges[ , "from"], "|", old_edges[ , "to"])
     new_edge_names <- names(edge_data)
     old2new <- .find_old2new_mapping(old_edge_names, new_edge_names)
     old_edges <- old_edges[old2new, -(1:2), drop=FALSE]
     edge_data <- lapply(seq_along(edge_data),
                             function(i) {
                                 old_data <- as.list(old_edges[i, ])
                                 new_data <- edge_data[[i]]
                                 new_data[names(old_data)] <- old_data
                                 new_data
                             })
     names(edge_data) <- new_edge_names
     edge_data
 }
 
 .igraph.to.graphNEL2 <- function(graph)
 {
     ans <- igraph.to.graphNEL(graph)
     ## We use direct slot access instead of the edgeData() accessor (which
     ## is broken on a graph with more than 1 edge between the same 2 nodes).
     ans@edgeData@data <- .repair_edge_data(ans@edgeData@data, graph)
     ans
b0b9f841
 }
 
39ca8ad2
 
 ### - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 ### make_Ragraph_from_igraph()
 ###
6c777389
 ### NOT exported
 ###
39ca8ad2
 
 ### Mappings from igraph attribute names to Ragraph attribute names.
 .igraph2Ragraph_EDGE_ATTRNAMES <- c(
62d41418
     width="lwd",
b0b9f841
     label.color="fontcolor"
     #lty="style"
 )
 
39ca8ad2
 .igraph2Ragraph_NODE_ATTRNAMES <- c(
b0b9f841
     color="fillcolor",
39ca8ad2
     .igraph2Ragraph_EDGE_ATTRNAMES
b0b9f841
 )
 
 .safeTranslateAttrNames <- function(attr_names, old2new)
 {
     m <- match(attr_names, names(old2new), nomatch=0L)
     idx <- which(m != 0L)
     new_names <- old2new[m]
     do_it <- !(new_names %in% attr_names)
     attr_names[idx[do_it]] <- new_names[do_it]
     attr_names
 }
 
39ca8ad2
 ### Safer than using sub().
 .safeTranslateEdgeNames <- function(edge_names, old.sep="|", new.sep="~")
 {
     edge_names <- strsplit(edge_names, old.sep, fixed=TRUE)
     stopifnot(all(elementLengths(edge_names) == 2L))  # sanity check
     edge_names <- unlist(edge_names, use.names=FALSE)
     paste0(edge_names[c(TRUE, FALSE)], new.sep, edge_names[c(FALSE, TRUE)])
 }
 
b0b9f841
 .make_Ragraph_nodeAttrs_from_graphNEL <- function(graph_nel)
 {
     attr_names <- names(nodeDataDefaults(graph_nel))
     node_data <- nodeData(graph_nel)
     node_names <- names(node_data)
     node_attrs <- lapply(attr_names,
                          function(attr_name)
                              unlist(lapply(node_data, `[[`, attr_name),
                                     recursive=FALSE))
     names(node_attrs) <- .safeTranslateAttrNames(attr_names,
39ca8ad2
                                  .igraph2Ragraph_NODE_ATTRNAMES)
b0b9f841
     is_null <- sapply(node_attrs, is.null)
     node_attrs <- node_attrs[!is_null]
 
     node_fontsize <- rep.int("12", length(node_names))
     names(node_fontsize) <- node_names
     node_attrs$fontsize <- node_fontsize
 
     node_attrs
 }
 
 .make_Ragraph_edgeAttrs_from_graphNEL <- function(graph_nel)
 {
     attr_names <- names(edgeDataDefaults(graph_nel))
39ca8ad2
     ## We use direct slot access instead of the edgeData() accessor (which
     ## is broken on a graph with more than 1 edge between the same 2 nodes).
     edge_data <- graph_nel@edgeData@data
6e72bb53
     edge_data_names <- names(edge_data)
     if (length(attr_names) == 0L || length(edge_data_names) == 0L)
         return(list())
     edge_data_names <- .safeTranslateEdgeNames(edge_data_names)
 
     ## edgeNames() is broken on graphNEL objects that have more than 1 edge
     ## between the same 2 nodes, hence the use of unique() in the sanity check.
     edge_names <- edgeNames(graph_nel)
     stopifnot(identical(unique(edge_data_names), edge_names))  # sanity check
 
     names(edge_data) <- edge_data_names
b0b9f841
     edge_attrs <- lapply(attr_names,
                          function(attr_name)
                              unlist(lapply(edge_data, `[[`, attr_name),
                                     recursive=FALSE))
     names(edge_attrs) <- .safeTranslateAttrNames(attr_names,
39ca8ad2
                                  .igraph2Ragraph_EDGE_ATTRNAMES)
b0b9f841
     is_null <- sapply(edge_attrs, is.null)
     edge_attrs <- edge_attrs[!is_null]
 
6e72bb53
     edge_fontsize <- rep.int("10", length(edge_data_names))
     names(edge_fontsize) <- edge_data_names
b0b9f841
     edge_attrs$fontsize <- edge_fontsize
 
     edge_attrs
 }
 
39ca8ad2
 ### Replacement for Rgraphviz::buildEdgeList().
 ### Rgraphviz::buildEdgeList() is broken when some of the components in the
 ### 'edgeAttrs' list have duplicated edge names on them (which only happens
 ### when the graph has more than 1 edge between the same 2 nodes).
 ### Note that .buildEdgeList2() assumes that 'edgeAttrs' was extracted from
 ### 'graph' with .make_Ragraph_edgeAttrs_from_graphNEL(), and that 'graph'
 ### still contains the original edge data. Because of this, .buildEdgeList2()
 ### is not a serious candidate for replacing Rgraphviz::buildEdgeList().
 
 .get_pEdge_fixed_attrs <- function(pedge_attrs, edge_names, edge_pos, edgeAttrs)
 {
     edge_name <- edge_names[edge_pos]
     ## Check and fix 'attrs' slot.
     attrs0 <- sapply(edgeAttrs,
                      function(edge_attr) {
                          idx <- which(names(edge_attr) == edge_name)
                          if (length(idx) == 0L)
                              return(NA)
                          if (length(idx) == 1L)
                              return(edge_attr[[idx]])
                          if (!identical(names(edge_attr), edge_names))
                              stop("cannot fix pEdge nb ", edge_pos)
                          edge_attr[[edge_pos]]
                      })
     if (!is.character(attrs0))
         storage.mode(attrs0) <- attrs0
     attrs0 <- attrs0[!is.na(attrs0)]
     attrs0_names <- names(attrs0)
     m <- match(attrs0_names, names(pedge_attrs))
     stopifnot(!any(is.na(m)))
     attrs1 <- unlist(pedge_attrs[attrs0_names], recursive=FALSE)
     as.list(attrs0[attrs1 != attrs0])
 }
 
 .buildEdgeList2 <- function(graph, recipEdges=c("combined", "distinct"),
                             edgeAttrs=list(), subGList=list(),
                             defAttrs=list())
 {
     ans <- buildEdgeList(graph, recipEdges=recipEdges,
                          edgeAttrs=edgeAttrs, subGList=subGList,
                          defAttrs=defAttrs)
     ans_names <- names(ans)  # the edge names
     ## Checking that 'graph' still contains the original edge data.
     ## We use direct slot access instead of the edgeData() accessor (which
     ## is broken on a graph with more than 1 edge between the same 2 nodes).
     stopifnot(identical(ans_names,
               .safeTranslateEdgeNames(names(graph@edgeData@data))))
     ans <- lapply(seq_along(ans),
                   function(i) {
                       pedge <- ans[[i]]
                       ## Check 'from' and 'to' slots.
                       edge_name <- paste0(from(pedge), "~", to(pedge))
                       stopifnot(identical(edge_name, ans_names[i]))
                       ## Get the fixed attribs (as a named list).
                       fixed_attrs <- .get_pEdge_fixed_attrs(pedge@attrs,
                                                             ans_names, i,
                                                             edgeAttrs)
                       ## Set them.
                       pedge@attrs[names(fixed_attrs)] <- fixed_attrs
                       pedge
                   })
     names(ans) <- ans_names
     ans
 }
 
808fe39e
 .make_Ragraph_from_graphNEL <- function(graph_nel)
b0b9f841
 {
     node_attrs <- .make_Ragraph_nodeAttrs_from_graphNEL(graph_nel)
     edge_attrs <- .make_Ragraph_edgeAttrs_from_graphNEL(graph_nel)
39ca8ad2
     ## Passing 'edge_attrs' directly to agopen() (thru the 'edgeAttrs' arg) is
     ## broken when there is more than 1 edge between the same 2 nodes, so we
     ## need to build and pass the lists of pNode and pEdge objects instead.
808fe39e
     #agopen(graph_nel, name=NA, nodeAttrs=node_attrs, edgeAttrs=edge_attrs)
39ca8ad2
     nodes <- buildNodeList(graph_nel, nodeAttrs=node_attrs)
     edges <- .buildEdgeList2(graph_nel, edgeAttrs=edge_attrs)
808fe39e
     agopen(name=NA, nodes=nodes, edges=edges, edgeMode=edgemode(graph_nel))
b0b9f841
 }
 
808fe39e
 make_Ragraph_from_igraph <- function(igraph)
62d41418
 {
808fe39e
     .make_Ragraph_from_graphNEL(.igraph.to.graphNEL2(igraph))
62d41418
 }