Skip to content

Instantly share code, notes, and snippets.

@timelyportfolio
Created January 25, 2017 20:52
Show Gist options
  • Save timelyportfolio/94c44959cf73e96a12ee2e194aaaaf2a to your computer and use it in GitHub Desktop.
Save timelyportfolio/94c44959cf73e96a12ee2e194aaaaf2a to your computer and use it in GitHub Desktop.
igraph layout editing with mapedit

This is certainly not the intended purpose of mapedit, but I thought it would be fun to use some accumulated R knowledge to use mapedit and leaflet to edit an igraph layout. To run the code, make sure to run the edit_map and Shiny parts separately.

library(igraph)
library(leaflet)
library(mapedit)

karate <- graph.famous("Zachary")
igrf_layout <- layout.auto(karate)

# see a default plot with our layout
plot(karate, layout=igrf_layout)

# plot with leaflet
lf <- leaflet(
  igrf_layout,
  options = leafletOptions(
    crs = leafletCRS(crsClass = "L.CRS.Simple")
  )
) %>%
  addCircleMarkers(group = "network")

new_layout <- lf %>%
  edit_map("network")

# this gets real tricky
#   but we will find a much easier way in mapedit
#   eventually
library(shiny)
shinyApp(
  htmlwidgets::onRender(
    lf,
"
function(el,x) {
  var lf = this;
  setTimeout(
    function(){
      Shiny.onInputChange(
        'getpoints',
        Object.keys(lf.layerManager.getLayerGroup('network')._layers)
      )
    },
    500
  )
}
"
  ),
  function(input, output) {
    observeEvent(input$getpoints, {leafids <<- input$getpoints})
  }
)

# now use our hacked method of id retrieval to identify points
library(dplyr)
library(purrr)

layout_df <- data.frame(igrf_layout) %>%
  mutate(leafid = leafids) %>%
  left_join(
    map_df(
      new_layout$edited[[1]]$features,
      ~data.frame(
        "leafid" = as.character(.x$properties["_leaflet_id"]),
        "newx" = .x$geometry$coordinates[[1]],
        "newy" = .x$geometry$coordinates[[2]],
        stringsAsFactors = FALSE
      )
    ) %>%
      set_names(c("leafid", "newx", "newy"))
  )

layout_df <- layout_df %>%
  mutate(X1 = ifelse(is.na(newx),X1,newx)) %>%
  mutate(X2 = ifelse(is.na(newy), X2, newy))

plot(karate, layout=data.matrix(layout_df[,1:2]))
@mdsumner
Copy link

mdsumner commented Feb 17, 2017

I'm really interested in getting this cross over working.

Here I build a graph from an sf object using a "common form" data structure. The PRIMITIVE function decomposes simple features into edges based on the paths the standard uses.

This is a simple polygon layer, the first feature has two disconnected paths (a polygon with a hole), the second is a simple polygon that shares a single edge with the first.

#devtools::install_github(c("mdsumner/sc", "mdsumner/scsf"))
library(scsf)
## Loading required package: sc
data("minimal_mesh")  ## sf layer
plot(minimal_mesh)

image

In sc primitives form, we already have the indexes of the vertices and the edges identified. (Efficient unique ID management is on the todo list ...)

The segment table of the primitives is already the right form for igraph, with a minor renaming. In this form we can pull out the topology in pure form and work with it.

prim <- sc::PRIMITIVE(minimal_mesh)
library(igraph)
library(dplyr)
g <- igraph::graph_from_data_frame(prim$segment %>% rename(from = .vertex0, to = .vertex1))

plot(g)  ## plot the abstract topology 
## We can override the layout using the actual vertices from the original features. 
V(g)$x <- prim$vertex$x_[match(names(V(g)), prim$vertex$vertex_)]
V(g)$y <- prim$vertex$y_[match(names(V(g)), prim$vertex$vertex_)]
plot(g)

image

(The hierarchy in the sc tables keeps track of the higher levels of grouping so we could pull out the edges and "do stuff").
Ultimately, I think we can bring this topology structure up to the user level, so that we can provide finely honed editing and interactivity.

(Thanks!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment