
Introduction
g6R continues to evolve as the go-to solution for interactive network visualization in R, especially for Shiny applications. With g6R 0.5.0, we focused on improving robustness, flexibility, and user experience. This release introduces new features and API improvements for data validation.
To quickly get started, install it with:
# CRAN
install.packages("g6R")
# With pak: https://pak.r-lib.org/
pak::pak("g6R")
# Latest GitHub main
pak::pak("cynkra/g6R")
Main changes
Data validation and helper functions
g6R now validates input data more strictly. We added g6_nodes(),
g6_edges() and g6_combos() helpers for creating their respective
graph elements. We suggest to use these helper functions instead of
passing lists or dataframes to g6 as they provide additional safety
checks. For convenience and to support usecases we may not have thought
about, we exported coercion functions for all elements such as
as_g6_nodes() or as_g6_data(). We use them internally to maintain
backward compatibility while ensuring better data validation. See below
an example of the various ways to create nodes:
# Create a single node
node <- g6_node(id = "A", type = "circle", style = list(fill = "#FFB6C1"))
# Create multiple nodes from a data frame
df <- data.frame(id = c("A", "B"), type = c("circle", "rect"))
nodes <- as_g6_nodes(df)
# With g6_nodes()
nodes <- g6_nodes(
g6_node(id = "A", type = "circle"),
g6_node(id = "B", type = "rect")
)
# with a list
lst <- list(
list(id = "A", type = "circle"),
list(id = "B", type = "rect")
)
nodes <- as_g6_nodes(lst)
#> [[1]]
#> $id
#> [1] "A"
#>
#> $type
#> [1] "circle"
#>
#> attr(,"class")
#> [1] "g6_node" "g6_element"
#>
#> [[2]]
#> $id
#> [1] "B"
#>
#> $type
#> [1] "rect"
#>
#> attr(,"class")
#> [1] "g6_node" "g6_element"
#>
#> attr(,"class")
#> [1] "g6_nodes"
In g6R, the recommended way to assemble graph data is by using the
helper functions g6_data() and as_g6_data():
# Create nodes and edges
my_nodes <- data.frame(id = c("A", "B"))
my_edges <- data.frame(source = "A", target = "B")
# Assemble graph data using g6_data()
graph <- g6_data(
nodes = my_nodes,
edges = my_edges
)
graph
#> $nodes
#> [[1]]
#> $id
#> [1] "A"
#>
#> attr(,"class")
#> [1] "g6_node" "g6_element"
#>
#> [[2]]
#> $id
#> [1] "B"
#>
#> attr(,"class")
#> [1] "g6_node" "g6_element"
#>
#> attr(,"class")
#> [1] "g6_nodes"
#>
#> $edges
#> [[1]]
#> $source
#> [1] "A"
#>
#> $target
#> [1] "B"
#>
#> $id
#> [1] "A-B"
#>
#> attr(,"class")
#> [1] "g6_edge" "g6_element"
#>
#> attr(,"class")
#> [1] "g6_edges"
#>
#> attr(,"class")
#> [1] "g6_data"
# Or use as_g6_data() directly with a list
lst <- list(
nodes = list(
list(id = "A"),
list(id = "B")
),
edges = list(
list(source = "A", target = "B")
)
)
graph2 <- as_g6_data(lst)
all.equal(graph, graph2)
#> [1] TRUE
Existing g6R code is unlikely to break as backwards compatibility was ensured during the development of this release. However, passing invalid parameters will now error instead of sending broken data to JavaScript, and we believe this is for the best!
Support for svg rendering
By default, G6 renders elements on the canvas, which means graph
elements are not part of the DOM. It’s been a bit of a blocker for us
for headless testing with shinytest2 because we could not targert or
trigger specific actions. Now, in g6_options() you can select an SVG
renderer like so:
g6_options(renderer = JS("() => new SVGRenderer()"))
This way, all graph elements are part of the DOM and can be targeted with shinytest2.
Layout updates
In some cases, particularly with the antv_dagre_layout() any node
addition or removal may rearrange the graph in unexpected ways, which
was confusing for users. Now, most proxy function like g6_add_nodes()
won’t recalculate the layout by default. You can still manually call
g6_update_layout() or set some options:
# Revert to the old behavior
options("g6R.layout_on_data_change" = TRUE)
As a side note g6R.preserve_elements_position option can be used in
combination with g6R.layout_on_data_change so that existing nodes
actually keep their old position when adding new data. Be aware that
this may not work well with combos and only if
g6_options(animation = FALSE). We are currently investigating better
ways to handle this.
Shiny updates
Capture mouse position
We exposed a new Shiny input, that is
input[["<graph_ID>-mouse_position"]] which can serve to add nodes at
the current mouse position. The value is a list with x and y
coordinates. Utilised with the create_edge() behavior, you can now
implement drag-and-drop node creation as shown in the example below:
library(shiny)
library(g6R)
library(bslib)
nodes <- data.frame(id = 1)
ui <- page_fluid(
g6_output("graph")
)
server <- function(input, output, session) {
output$graph <- render_g6({
g6(nodes = nodes) |>
g6_layout() |>
g6_behaviors(
create_edge(
enable = JS("
(e) => { return e.shiftKey; }"
),
onFinish = JS(
"(edge) => {
const graph = HTMLWidgets.find('#graph').getWidget();
const targetType = graph.getElementType(edge.target);
if (targetType !== 'node') {
graph.removeEdgeData([edge.id]);
} else {
Shiny.setInputValue('added_edge', edge);
}
}"
)
)
)
})
next_id <- reactiveVal(2)
observeEvent(input$added_edge, {
edge <- input$added_edge
pos <- input[["graph-mouse_position"]]
# Only add node if released on canvas
if (edge$targetType == "canvas") {
# Add new node at drop position
g6_proxy("graph") |>
g6_add_nodes(
g6_node(
id = next_id(),
style = list(x = pos$x, y = pos$y)
)
)
# Connect source node to new node
g6_proxy("graph") |>
g6_add_edges(
g6_edge(source = edge$source, target = next_id())
)
next_id(next_id() + 1)
}
})
}
shinyApp(ui, server)
The above is possible owing to a modification in create_edge(). By
default, it was not possible to release the edge drawer on the canvas,
but now, we allow this through input$added_edge$targetType.
Selection detection
Elements selected via brush_select() or lasso_select() have a custom
input handler, which helps to differentiate between different modes of
selection.:
input[["<graph_ID>-selected_combo"]]
[1] "1" "2"
attr(,"eventType")
[1] "brush_select"
Context menu
We expose input$<graph_ID>-contextmenu which contains information
about the element right-clicked by the user:
library(shiny)
library(g6R)
library(bslib)
nodes <- data.frame(id = c("node1", "node2"))
edges <- data.frame(source = "node1", target = "node2")
ui <- page_fluid(
g6_output("graph"),
verbatimTextOutput("contextmenu_info")
)
server <- function(input, output, session) {
output$graph <- render_g6({
g6(
nodes = nodes,
edges = edges
) |>
g6_layout() |>
g6_plugins(
context_menu()
)
})
output$contextmenu_info <- renderPrint({
input[["graph-contextmenu"]]
})
}
shinyApp(ui, server)
Prod and dev modes
We added options("g6R.mode) which can be set to "dev" or "prod".
In dev mode, g6R will display any JS error in a Shiny notification. This
may be helpful for you to debug any data issue and also for us while
testing the package.
Bug fixes and minor improvements
We fixed a bunch of issues in the select behaviors (click, brush). You can review the full list here
Next steps
If you have any feedback, you can reach out to us via the contact page on this site. Alternatively, please report any issues or feature requests on the GitHub repository.