cynkra


Introducing g6R 0.1.0: a network widget for R.

From the Blog
Shiny
R

Author(s)

David Granjon

David Schoch

Published

Hex Sticker of the R package g6R. It shows a colorful database icon.

Why?

Network widgets for R exist but few are really feature comprehensive when it comes to work with Shiny apps. To our knowledge, the most complete is visNetwork, which is a wrapper around the vis.js JavaScript library. It provides a lot of features, but its underlying JS library is not actively maintained anymore and has some limitations in terms flexibility, especially in 2025.

Other alternatives like networkD3, cytoscape js wrappers like RCy3 or sigmajs are available, but some lack Shiny bindings and other features.

In our recent blockr framework project with the pharma company BMS, we needed a modern network engine that user can easily interact with in a Shiny app. None of the above solutions could fully meet our needs until we discovered G6.

G6

G6 is a JavaScript library which powers a graph visualisation engine. It is very well documented and full of neat features. In a nutchell, G6 supports nested nodes, animations, fancy node grouping (see image below), fully customisable context menu plugins, interactive edge creation and many more. It is also part of a broader visualisation ecosystem built by the AntV group for which we can expect a long term support.

g6 bubble set to group nodes

g6R

g6R is an HTML widget wrapping the G6 library for R. It is available on CRAN as of version 0.1.0.

Demo

In the below demonstration, you can see nodes, edges and combos. Combos are a convenient way to group nodes together.

Install

To quickly get started, install it with:

# dev
pak::pak("cynkra/g6R")

# CRAN
pak::pak("g6R")

Basics

g6R object layers

With g6R you build your graph layer by layer similar to how you would do with ggplot2:

  • g6() creates a new graph and expects data like nodes, edges. You may also pass an url pointing to a remote JSON file containing the data, which is better for performances as data don’t need to be serialized to JSON in R before being sent to JS.
  • g6_options() allows to control global options at the graph level or to set default styles for nodes, edges and combos.
  • g6_layout() sets the layout of the graph.
  • g6_behaviors() adds interactive behaviors to the graph. Behaviors provide more interactivity to the graph, such as zooming, dragging, and selecting elements.
  • g6_plugins() adds plugins to the graph. Plugins are additional features that can be added to the graph, such as a mini-map, toolbars, and grid lines.

In the below example, we create a graph with pokemon evolution data containing nodes, edges and combos. We then set the layout to a combo combined layout, which is a multi layers layout that combines the nodes and combos in a single layout. We also set some options for the graph, such as the animation, the node style, the edge end arrow style and the combo palette. When we call behaviors or plugins, we can either pass the name of the element or use the corresponding function. For example, g6_behaviors("zoom-canvas") is equivalent to g6_behaviors(zoom_canvas()). The latter allows to pass more options.

g6(poke$nodes, poke$edges, poke$combos) |>
  g6_layout(combo_combined_layout(spacing = 50)) |>
  g6_options(
    animation = FALSE,
    node = list(
      style = list(
        labelText = JS(
          "(d) => {
            return d.data.label
          }"
        )
      )
    ),
    edge = list(
      style = list(endArrow = TRUE)
    ),
    combo = list(
      style = list(
        fillOpacity = 0.5
      ),
      palette = list(
        type = "group", # use discret palette
        field = "family",
        color = "spectral"
      )
    )
  ) |>
  g6_behaviors(
    zoom_canvas(),
    collapse_expand(),
    drag_canvas(),
    drag_element()
  ) |>
    g6_plugins("toolbar")

Shiny

That’s the main reason why we built g6R! It is designed to work seamlessly with Shiny, allowing you to modify your graph directly from the server function and dynamically interact with nodes and edges.

Few inputs are accessible from the server function like input[[<GRAPH_ID>-state]] which contains a list representation of the current graph state. It contains the nodes, edges and combos, as well as current options and more. This is useful to save a graph and restore it later. To avoid timing issues when interacting with the graph, you can know whether the graph is ready to be modified with input[[<GRAPH_ID>-initialized]]. Selected elements also get their input like input[[<GRAPH_ID>-selected_node]], input[[<GRAPH_ID>-selected_edge]], input[[<GRAPH_ID>-selected_combo]].

To get the state of a given node, you can query it with g6_get_nodes() and access input[[<GRAPH_ID>-<NODE_ID>-state"]]:

observeEvent(
  {
    req(input[["graph-initialized"]])
    input[["graph-state"]]
  },
  {
    g6_proxy("graph") |> g6_get_nodes(c("node1", "node2"))
  }
)

output$node2_state <- renderPrint(input[["graph-node2-state"]])

We exposed dozens of functions like:

  • g6_add_nodes(), g6_add_edges(), g6_add_combos() to add nodes, edges and combos.
  • g6_update_nodes(), g6_update_edges(), g6_update_combos() to update nodes, edges and combos.
  • g6_remove_nodes(), g6_remove_edges(), g6_remove_combos() to remove nodes, edges and combos.
  • g6_set_nodes(), g6_set_edges(), g6_set_combos() to select nodes, edges and combos or set another state like disabled.
  • g6_expand_combos(), g6_collapse_combos() to expand and collapse combos.
  • g6_update_plugin(), g6_update_behavior() to update plugins and behaviors, respectively.

They can be used as follows with g6_proxy():

# Some part of the code is omitted ...
observeEvent(input$remove_node, {
  g6_proxy("graph") |> # Pass the ID of the graph widget
    g6_remove_nodes("node1")
})

observeEvent(input$add_node, {
  g6_proxy("graph") |>
    g6_add_nodes(
      data.frame(
        id = c("node3", "node4"),
        combo = c("combo2", "combo2")
      )
    ) |>
    g6_add_edges(
      data.frame(
        source = "node3",
        target = "node4"
      )
    )
})

observeEvent(input$update_behavior, {
  g6_proxy("graph") |>
    g6_update_behavior(
      key = "zoom-canvas",
      enable = FALSE
    )
})

We invite the reader to look at the various examples here.

igraph support

As discussed in a previous blog post, cynkra is actively engaged into improving igraph, thanks to our dedicated team of developers Maëlle Salmon, David Schoch and Kirill Müller.

We made sure that g6R supports igraph objects, allowing you to convert your existing igraph graphs into g6R graphs.

library(igraph)
set.seed(123)
V(kite)$fill <- sample(
  c("red", "green", "blue"),
  vcount(kite),
  replace = TRUE
)
E(kite)$stroke <- sample(
  c("red", "green", "blue"),
  ecount(kite),
  replace = TRUE
)
E(kite)$lineWidth <- 3

g6_igraph(kite) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(drag_element_force())

You can find more details about the igraph support in the documentation.

Performance

g6R is made for performance as it can handle large graphs with thousands of nodes and edges.

What’s next?

We are excited about the future of g6R and what you can build with it. We are currently using it in a top notch Shiny app project in the blockr ecosystem. Therefore, expect more exciting features to come in the future.