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.
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
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.