13  Interactive Visualization

The previous chapters focused on creating static network plots with ggraph. Static visualizations are essential for publications and reports, but interactive visualizations open up a different mode of exploration: users can pan across the canvas, zoom into dense regions, hover over nodes for details, and drag elements to untangle complex structures. This chapter surveys the landscape of interactive network visualization in R and then provides a thorough introduction to g6R, a modern and feature-rich package for building interactive network graphics.

13.1 Packages Needed for this Chapter

library(igraph)
library(networkdata)
library(visNetwork)
library(networkD3)
library(threejs)
library(g6R)

The packages visNetwork, networkD3, and threejs only appear briefly in the overview. The remainder of the chapter focuses on g6R.

13.2 Data Preparation

We use Zachary’s karate club network, which also appears in the clustering chapter. With 34 nodes and 78 edges, it is small enough that every interactive example in this chapter renders without lag.

is_html <- knitr::is_html_output()
data("karate")
V(karate)$name <- as.character(seq_len(vcount(karate)))

palette4 <- c("#1A5878", "#C44237", "#AD8941", "#E99093")

## compute a clustering for node colors
V(karate)$clu <- as.character(membership(cluster_louvain(karate)))

## compute degree as node size
V(karate)$size <- degree(karate)

13.3 Interactive Network Visualization Tools in R

There exist several R packages that can produce interactive network visualizations, each wrapping a different JavaScript library. We briefly survey three popular options before diving into g6R.

13.3.1 visNetwork

The visNetwork package wraps the vis.js library and offers a broad toolkit for interactive networks. Converting an igraph object requires mapping a few vertex attributes to the column names that visNetwork expects.

## visNetwork expects columns: label, group, value
V(karate)$label <- V(karate)$name
V(karate)$group <- V(karate)$clu
V(karate)$value <- V(karate)$size

vis_data <- toVisNetworkData(karate)
visNetwork(vis_data$nodes, vis_data$edges, width = "100%", height = "400px") |>
  visOptions(highlightNearest = TRUE)
Figure 13.1: Karate club network rendered with visNetwork.

Out of the box, visNetwork provides draggable nodes, zoom, and hover highlighting. It is well suited for quick interactive previews that do not require much customization.

13.3.2 networkD3

The networkD3 package wraps D3.js and produces force-directed layouts.

d3_data <- igraph_to_networkD3(karate,
  group = as.numeric(V(karate)$clu)
)

forceNetwork(
  Links = d3_data$links,
  Nodes = d3_data$nodes,
  Source = "source",
  Target = "target",
  NodeID = "name",
  Group = "group",
  opacity = 0.9,
  zoom = TRUE,
  fontSize = 12
)
Figure 13.2: Karate club network rendered with networkD3.

13.3.3 threejs

The threejs package renders networks in 3D using WebGL. The result is visually striking, although 3D layouts can make it harder to judge distances and read labels.

graphjs(karate,
  vertex.size = V(karate)$size * 0.2,
  vertex.color = palette4[as.numeric(V(karate)$clu)]
)
Figure 13.3: Karate club network in 3D with threejs.

Each of these packages has its place. visNetwork is mature and full-featured, networkD3 leverages the power of D3.js, and threejs offers 3D rendering. For the remainder of this chapter we focus on g6R, which provides the most comprehensive framework for interactive graph visualization in R, with deep igraph integration, a rich plugin system, and high-performance rendering.

13.4 Getting Started with g6R

The g6R package is an R binding to AntV’s G6 graph visualization engine. It produces htmlwidgets that render in Quarto documents, the RStudio viewer, and Shiny applications. For igraph users, the key entry point is g6_igraph().

13.4.1 A First Interactive Network

The simplest way to get an interactive visualization is to pass an igraph object directly to g6_igraph().

g6_igraph(karate)
Figure 13.4: Karate club network with g6R using default settings.

With no additional configuration, g6R applies a default force-directed layout and renders an interactive canvas where you can drag nodes and zoom. The next sections show how to progressively customize this output.

13.4.2 The g6R Workflow

Customization in g6R follows a pipe-based workflow. Starting from the initial graph, you chain together functions that each control a different aspect of the visualization:

g6_igraph(karate) |>
  g6_layout(...)     |>   # spatial arrangement
  g6_options(...)    |>   # node/edge styling
  g6_behaviors(...)  |>   # user interactions
  g6_plugins(...)          # add-on features

Each of these building blocks is covered in its own section below.

13.5 Layouts

Layout algorithms determine where nodes are placed on the canvas. Unlike the static layouts computed by igraph or graphlayouts, the layouts in g6R run client-side in the browser. Force-directed layouts animate as nodes settle into position, making the layout process itself part of the interactive experience.

13.5.1 Force-Directed Layouts

The default layout in g6R is d3_force_layout(), which simulates physical forces to position nodes. Linked nodes attract each other while all nodes repel, producing a layout that tends to place densely connected groups close together.

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(drag_canvas(), zoom_canvas(), drag_element_force())
Figure 13.5: Karate club network with D3 force-directed layout.

Try dragging a node in Figure 13.5: the force simulation reacts in real time, and neighboring nodes adjust their positions accordingly. The fruchterman_layout() is another force-directed option based on the Fruchterman-Reingold algorithm.

g6_igraph(karate) |>
  g6_layout(fruchterman_layout()) |>
  g6_behaviors(drag_canvas(), zoom_canvas(), drag_element_force())
Figure 13.6: Karate club network with Fruchterman-Reingold layout.

13.5.2 Circular and Radial Layouts

A circular layout arranges all nodes evenly along a circle, which can be useful for spotting symmetric structures or for smaller networks.

g6_igraph(karate) |>
  g6_layout(circular_layout()) |>
  g6_behaviors(drag_canvas(), zoom_canvas())
Figure 13.7: Karate club network with circular layout.

A radial layout places a focal node at the center and arranges the remaining nodes in concentric rings based on their graph distance from the focus. This is useful for ego-centric exploration.

g6_igraph(karate) |>
  g6_layout(radial_layout(focusNode = "1")) |>
  g6_behaviors(drag_canvas(), zoom_canvas())
Figure 13.8: Karate club network with radial layout centered on node 1.

13.5.3 Concentric Layout

The concentric layout arranges nodes in concentric circles sorted by a numeric attribute. Here we use degree centrality, placing the most connected characters at the center.

## g6R needs a 'degree' attribute on the nodes
V(karate)$degree <- degree(karate)

g6_igraph(karate) |>
  g6_layout(concentric_layout(sortBy = "degree")) |>
  g6_behaviors(drag_canvas(), zoom_canvas())
Figure 13.9: Karate club network with concentric layout sorted by degree.

13.6 Styling Nodes and Edges

A bare g6_igraph() call produces a functional but unstyled visualization. There are two main approaches to styling: setting attributes directly on the igraph object, or using g6_options() for global configuration.

13.6.1 Styling via igraph Attributes

The g6_igraph() function reads vertex and edge attributes that match G6 style property names. For example, setting a fill attribute on vertices controls node color.

## Map community membership to fill color
V(karate)$fill <- palette4[as.numeric(V(karate)$clu)]
V(karate)$stroke <- "#555555"
V(karate)$lineWidth <- 1.5

## Style edges
E(karate)$stroke <- "#cccccc"

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(drag_canvas(), zoom_canvas(), drag_element_force())
Figure 13.10: Karate club network styled via igraph vertex and edge attributes.

This approach is natural for igraph users: styling stays close to the data, and the mapping is straightforward.

13.6.2 Styling via g6_options()

For more control, g6_options() lets you set global defaults using node_options() and edge_options(). You can also use JavaScript callbacks via htmlwidgets::JS() to create data-driven styling.

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_options(
    node = node_options(
      style = node_style_options(
        labelText = JS("(d) => d.id"),
        labelFontSize = 10
      )
    ),
    edge = edge_options(
      style = edge_style_options(
        stroke = "#999999",
        lineWidth = 0.5
      )
    )
  ) |>
  g6_behaviors(drag_canvas(), zoom_canvas(), drag_element_force())
Figure 13.11: Karate club network styled with g6_options().

The JS() function from htmlwidgets lets you pass small JavaScript expressions that are evaluated for each element. In this example, (d) => d.id extracts the node identifier to use as a label.

13.7 Behaviors

Behaviors define how users can interact with the visualization. They are added through g6_behaviors() and can be mixed freely.

13.7.2 Element Interaction

Beyond canvas navigation, users can interact with individual elements. drag_element_force() lets you reposition nodes while the force simulation adjusts around them. Setting fixed = TRUE keeps a node where you drop it rather than letting the simulation pull it back. hover_activate() highlights a node and its direct neighbors on mouseover. click_select() allows selecting individual nodes with a click.

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(
    drag_canvas(),
    zoom_canvas(),
    drag_element_force(fixed = TRUE),
    hover_activate(),
    click_select(multiple = TRUE)
  )
Figure 13.13: Karate club network with drag, hover, and click-select behaviors.

In Figure 13.13, try hovering over a node to see its neighborhood highlighted, or shift-click to select multiple nodes.

13.7.3 Lasso and Brush Selection

For selecting groups of nodes, g6R provides two area-selection tools. brush_select() draws a rectangular selection box, while lasso_select() allows freehand drawing around the nodes of interest.

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(
    drag_canvas(),
    zoom_canvas(),
    lasso_select(),
    brush_select()
  )
Figure 13.14: Karate club network with lasso and brush selection.

13.8 Plugins

Plugins add features on top of the base visualization. They are activated through g6_plugins() and can be combined freely.

13.8.1 Minimap

A minimap provides a thumbnail overview of the entire graph, which is useful for navigating large networks. The shaded rectangle shows the currently visible viewport.

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(drag_canvas(), zoom_canvas(), drag_element_force()) |>
  g6_plugins(minimap())
Figure 13.15: Karate club network with a minimap for navigation.

13.8.2 Tooltips

Tooltips display information when the user hovers over a node. By default, the tooltip shows the node’s data attributes.

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(drag_canvas(), zoom_canvas(), hover_activate()) |>
  g6_plugins(tooltips())
Figure 13.16: Karate club network with hover tooltips.

13.8.3 Visual Grouping: Hulls and Bubble Sets

In Figure 12.1 of the previous chapter, we used geom_mark_hull() from ggforce to highlight clusters in a static plot. The g6R package offers interactive equivalents. The hull() plugin draws convex polygons around groups of nodes, while bubble_sets() produces smoother, organic contours.

## Split nodes by community membership
clu_members <- split(V(karate)$name, V(karate)$clu)

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(drag_canvas(), zoom_canvas(), drag_element_force()) |>
  g6_plugins(
    hull(
      key = "hull-1", members = clu_members[["1"]],
      fill = palette4[1], stroke = palette4[1]
    ),
    hull(
      key = "hull-2", members = clu_members[["2"]],
      fill = palette4[2], stroke = palette4[2]
    ),
    hull(
      key = "hull-3", members = clu_members[["3"]],
      fill = palette4[3], stroke = palette4[3]
    ),
    hull(
      key = "hull-4", members = clu_members[["4"]],
      fill = palette4[4], stroke = palette4[4]
    )
  )
Figure 13.17: Karate club network with hull grouping by community.

The bubble_sets() plugin offers an alternative visual style. Instead of sharp convex hulls, it wraps groups in smooth, rounded contours that can overlap organically.

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(drag_canvas(), zoom_canvas(), drag_element_force()) |>
  g6_plugins(
    bubble_sets(
      key = "bs-1", members = clu_members[["1"]],
      fill = palette4[1], stroke = palette4[1]
    ),
    bubble_sets(
      key = "bs-2", members = clu_members[["2"]],
      fill = palette4[2], stroke = palette4[2]
    ),
    bubble_sets(
      key = "bs-3", members = clu_members[["3"]],
      fill = palette4[3], stroke = palette4[3]
    ),
    bubble_sets(
      key = "bs-4", members = clu_members[["4"]],
      fill = palette4[4], stroke = palette4[4]
    )
  )
Figure 13.18: Karate club network with bubble-set grouping.

13.8.4 Fisheye Lens

The fisheye plugin applies a focus-plus-context distortion: the area under the cursor is magnified while the rest of the graph remains visible at a reduced scale. This is particularly helpful for dense networks where zooming alone would lose the global context.

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(drag_canvas(), zoom_canvas()) |>
  g6_plugins(fish_eye())
Figure 13.19: Karate club network with fisheye distortion.

13.8.5 Legend

A legend plugin can display categorical groupings directly on the canvas.

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_behaviors(drag_canvas(), zoom_canvas(), drag_element_force()) |>
  g6_plugins(legend(nodeField = "clu"))
Figure 13.20: Karate club network with legend showing community membership.

13.9 Putting It All Together

The following example combines styled nodes and edges, interactive behaviors, and several plugins into a single polished visualization.

V(karate)$fill <- palette4[as.numeric(V(karate)$clu)]
V(karate)$stroke <- "#555555"
V(karate)$lineWidth <- 1

E(karate)$stroke <- "#cccccc"
E(karate)$lineWidth <- 0.5

g6_igraph(karate) |>
  g6_layout(d3_force_layout()) |>
  g6_options(
    node = node_options(
      style = node_style_options(
        labelText = JS("(d) => d.id"),
        labelFontSize = 10
      )
    )
  ) |>
  g6_behaviors(
    drag_canvas(),
    zoom_canvas(),
    drag_element_force(fixed = TRUE),
    hover_activate(),
    click_select(multiple = TRUE)
  ) |>
  g6_plugins(
    minimap(),
    tooltips(),
    legend(nodeField = "clu")
  )
Figure 13.21: Complete interactive karate club network with g6R.

This visualization supports panning and zooming, node dragging with force simulation, hover highlighting of neighborhoods, multi-selection via click, a minimap for navigation, tooltips for detail on demand, and a legend for community membership. The g6R package also offers first-class Shiny integration through g6_output(), render_g6(), and g6_proxy() for building fully reactive network applications. For more details, see the g6R documentation.