Visualizing GEXF graphs with sigma.js

Overview

rgexf ships with an interactive graph viewer built on top of sigma.js v3 and graphology. The widget is zero-dependency from the user’s perspective: the required JavaScript libraries are bundled inside the package, so no internet connection is needed at runtime.

The main entry point is plot() — the standard R S3 method dispatches to the sigma.js widget automatically for gexf objects. The underlying sigmajs() function is also exported for cases where you need extra arguments such as borderColor or explicit width/height.


1 A simple coloured graph

1.1 Creating the graph

We start by building a small three-node graph from scratch. rgexf stores visual properties (colour, position, size) through the nodesVizAtt argument of gexf().

library(rgexf)

# Node and edge tables
nodes <- data.frame(
  id    = 1:3,
  label = c("Alice", "Bob", "Carol")
)
edges <- data.frame(
  source = c(1L, 2L, 1L),
  target = c(2L, 3L, 3L)
)

# Visual attributes
node_colors <- data.frame(
  r = c(220L,  66L,  40L),
  g = c( 50L, 133L, 167L),
  b = c( 47L, 244L, 240L),
  a = c(  1,    1,    1)
)
node_positions <- data.frame(
  x = c(-1,  1,  0),
  y = c( 0,  0,  1.5),
  z = c( 0,  0,  0)
)
node_sizes <- c(8, 8, 8)

g <- gexf(
  nodes        = nodes,
  edges        = edges,
  nodesVizAtt  = list(
    color    = node_colors,
    position = node_positions,
    size     = node_sizes
  )
)

1.2 Plotting the graph

Calling plot() on a gexf object renders the sigma.js widget directly.

plot(g)

By default sigma.js renders nodes as plain filled circles — the node’s color fills the entire disc. You can add an optional border ring by passing borderColor (any extra arguments are forwarded to sigmajs()):

# White border ring (15 % of the node radius)
plot(g, borderColor = "#ffffff", borderSize = 0.15)

# Dark charcoal border, slightly thicker
plot(g, borderColor = "#333333", borderSize = 0.2)

2 The Les Misérables network

The package includes a classic co-occurrence network from Victor Hugo’s Les Misérables, originally prepared for Gephi. It already contains viz:color, viz:position, and viz:size attributes, so the widget picks them up automatically.

2.1 Reading the file

lesmi_path <- system.file("gexf-graphs/lesmiserables.gexf", package = "rgexf")
lesmi      <- read.gexf(lesmi_path)
lesmi
#> <?xml version="1.0" encoding="UTF-8"?>
#> <gexf xmlns="http://www.gexf.net/1.3" xmlns:viz="http://www.gexf.net/1.3/viz" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.3" xsi:schemaLocation="http://www.gexf.net/1.3 http://www.gexf.net/1.3/gexf.xsd">
#>   <meta lastmodifieddate="2016-11-09">
#>     <creator>Gephi 0.9</creator>
#>     <description/>
#>   </meta>
#>   <graph defaultedgetype="undirected" mode="static">
#>     <attributes class="node" mode="static">
#>       <attribute id="modularity_class" title="Modularity Class" type="integer"/>
#>     </attributes>
#>     <nodes>
#>       <node id="11" label="Valjean">
#>         <attvalues>
#>           <attvalue for="modularity_class" value="1"/>
#>         </attvalues>
#>         <viz:size value="100.0"/>
#>         <viz:position x="-87.93029" y="6.8120565"/>
#>         <viz:color r="245" g="91" b="91"/>
#>       </node>
#>       <node id="48" label="Gavroche">
#>         <attvalues>
#>           <attvalue for="modularity_class" value="8"/>
#>         </attvalues>
#>         <viz:size value="61.600006"/>
#>         <viz:position x="387.89572" y="-110.462326"/>
#>         <viz:color r="91" g="245" b="91"/>
#>       </node>
#>       <node id="55" label="Marius">
#>         <attvalues>
#>           <attvalue for="modularity_class" value="6"/>
#>         </attvalues>
#>         <viz:size value="53.37143"/>
#>         <viz:position x="206.44687" y="13.805411"/>
#>         <viz:color r="194" g="91" b="245"/>
#>       </node>
#>       <node id="27" label="Javert">
#>         <attvalues>
#>           <attvalue for="modularity_class" value="7"/>
#>         </attvalues>
#>         <viz:size value="47.88571"/>
#>         <viz:position x="-81.46074" y="204.20204"/>
#>         <viz:color r="91" g="245" b="194"/>
#>       </node>
#>       <node id="25" label="Thenardier">
#>         <attvalues>
#>           <attvalue for="modularity_class" value="7"/>
#>         </attvalues>
#>         <viz:size value="45.142853"/>
#>         <viz:position x="82.80825" y="203.1144"/>
#>         <viz:color r="91" g="245" b="194"/>
#>       </node>
#>       <node id="23" label="Fantine">
#>         <attvalues>
#>           <attvalue for="modularity_class" value="2"/>
#>         </attvalues>
#>         <viz:size value="42.4"/>
#>         <viz:position x="-313.42786" y="289.44803"/>
#>         <viz:color r="91" g="194" b="245"/>
#>       </node>
#>       <node id="58" label="Enjolras">
#>         <attvalues>
#>           <attvalue for="modularity_class" value="8"/>
#>         </attvalues>
#>         <viz:size value="42.4"/>
#>         <viz:position x="355.78366" y="74.882454"/>
#>         <viz:color r="91" g="245" b="91"/>
#>       </node>
#>       <node id="62" label="Courfeyrac">
#>         <attvalues>
#>           <attvalue for="modularity_class" value="8"/>
#>         </attvalues>
#>         <viz:size value="36.91429"/>
#>         <viz:position x="436.17184" y="12.7286825"/>
#>         <viz:color r="91" g="245" b="91"/>
#>       </node>
#>       <node id="64" label="Bossuet">
#>         <attvalues>
#>           <attvalue for="modularity_class" value="8"/>
#>         </attvalues>
#>         <viz:size value="36.91429"/>
#>         <viz:position x="455.81955" y="115.45826"/>
#>         <viz:color r="91" g="245" b="91"/>
#>       </node>
#>       <node id="63" label="Bahorel">
#>         <attvalues>
#>           <attvalue for="modularity_class" value="8"/>
#>         </attvalues>
#>         <viz:size value="34.17143"/>
#>         <viz:position x="602.55225" y="-16.421427"/>
#>         <viz:color r="91" g="245" b="91"/>
#>       </node>
#>          ...
#>      </nodes>
#>     <edges>
#>       <edge id="0" source="1" target="0"/>
#>       <edge id="1" source="2" target="0" weight="8.0"/>
#>       <edge id="2" source="3" target="0" weight="10.0"/>
#>       <edge id="3" source="3" target="2" weight="6.0"/>
#>       <edge id="4" source="4" target="0"/>
#>       <edge id="5" source="5" target="0"/>
#>       <edge id="6" source="6" target="0"/>
#>       <edge id="7" source="7" target="0"/>
#>       <edge id="8" source="8" target="0" weight="2.0"/>
#>       <edge id="9" source="9" target="0"/>
#>          ...
#>      </edges>
#>   </graph>
#> </gexf>

2.2 Default plot

plot(lesmi)

The colours come directly from the GEXF file. Each character group is assigned a distinct hue by the original dataset, so the community structure is immediately visible.

2.3 Adding node borders for contrast

The default rendering is sigma.js’s plain filled circle. If you want to separate overlapping nodes or simply add visual polish, pass a borderColor. A white outer ring works well on light backgrounds:

plot(lesmi, borderColor = "#ffffff", borderSize = 0.15)

The borderColor argument accepts any CSS colour string ("#rrggbb", "rgb(r,g,b)", named colours such as "white", etc.). The borderSize argument is a number between 0 and 1 representing the border width as a fraction of the node’s radius — 0.15 means the ring takes up 15 % of the node radius.

For a dark-themed background, try a darker value:

plot(lesmi, borderColor = "#222222", borderSize = 0.1)

3 Using the legacy gexf-js viewer

The older gexf-js-based viewer is preserved as plot_gexfjs() for users who need the file-server workflow:

plot_gexfjs(lesmi, copy.only = TRUE)
#> GEXF graph successfully written at:
#> /tmp/RtmpSxt0Zn/network.gexf

plot_gexfjs() copies the gexf-js viewer files together with the graph to a directory (tempdir() by default). When called interactively with the default copy.only = FALSE, it also starts a local HTTP server via servr::httd() and opens the graph in the default browser – useful for standalone HTML reports or for users who prefer the original gexf-js interface. In non-interactive sessions (such as when this vignette is built) only the files are copied. You can also embed the graph in a Shiny app or R Markdown using the gexfjs() function:

gexfjs(lesmi)

Session info

sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] rgexf_0.17.0   rmarkdown_2.31
#> 
#> loaded via a namespace (and not attached):
#>  [1] cli_3.6.6         knitr_1.51        rlang_1.3.0       xfun_0.60        
#>  [5] otel_0.2.0        promises_1.5.0    jsonlite_2.0.0    buildtools_1.0.0 
#>  [9] htmltools_0.5.9   maketools_1.3.2   httpuv_1.6.17     XML_3.99-0.23    
#> [13] sys_3.4.3         sass_0.4.10       evaluate_1.0.5    jquerylib_0.1.4  
#> [17] fastmap_1.2.0     yaml_2.3.12       lifecycle_1.0.5   compiler_4.6.1   
#> [21] igraph_2.3.3      Rcpp_1.1.2        htmlwidgets_1.6.4 pkgconfig_2.0.3  
#> [25] later_1.4.8       digest_0.6.39     R6_2.6.1          servr_0.33       
#> [29] magrittr_2.0.5    bslib_0.11.0      tools_4.6.1       cachem_1.1.0