# Development version with full features
# devtools::install_github("sonsoleslp/cograph")
# CRAN version
install.packages("cograph")
Network Visualization with cograph: A Complete Plotting Guide
2026-03-05
Source:vignettes/articles/1_cograph-tutorial-plotting.qmd
1 Introduction
Network visualization in R has traditionally meant choosing between two extremes: quick-and-dirty plots that look like hairballs, or heavily customized code that takes longer to write than the analysis itself. cograph collapses this tradeoff. The splot() function produces publication-ready network visualizations from a single call, with sensible defaults that you can override parameter by parameter.
This tutorial demonstrates everything through a single TNA model of collaborative regulation behaviors. We start with a zero-argument plot and progressively layer in customization — so you can see exactly what each parameter changes.
- The plot — one line, zero arguments, publication-ready
- Node styling — shapes, colors, sizes, labels, centrality-based scaling
- Edge styling — width, color, curvature, labels, thresholds
- Donuts and pies — composite node shapes for multivariate data
- Themes and palettes — pre-built visual themes
-
Pipe-friendly API — the
sn_*function family for chainable plot building - Specialized plots — chord diagrams, heatmaps, alluvial flows
- Group comparisons — difference networks and multi-group grids
- Bootstrap — validated network visualization
- Permutation testing — significant group differences
- Mixed networks — directed and undirected edges combined
For TNA-specific workflows, see the TNA tutorial.
GitHub Development Version
This tutorial covers the full feature set available in the GitHub development version of cograph. The CRAN release provides the core plotting engine; the development version adds themes, pipe API, specialized plots, and additional features.
2 Building the Model
We use the built-in group_regulation dataset from the tna package — coded collaborative regulation behaviors from student groups. This gives us a 9-state directed transition network, the kind of rich structure that showcases cograph’s capabilities.
State Labels :
adapt, cohesion, consensus, coregulate, discuss, emotion, monitor, plan, synthesis
Transition Probability Matrix :
adapt cohesion consensus coregulate discuss emotion
adapt 0.0000000000 0.27308448 0.47740668 0.02161100 0.05893910 0.11984283
cohesion 0.0029498525 0.02713864 0.49793510 0.11917404 0.05958702 0.11563422
consensus 0.0047400853 0.01485227 0.08200348 0.18770738 0.18802338 0.07268131
coregulate 0.0162436548 0.03604061 0.13451777 0.02335025 0.27360406 0.17208122
discuss 0.0713743356 0.04758289 0.32118451 0.08428246 0.19488737 0.10579600
emotion 0.0024673951 0.32534367 0.32040888 0.03419105 0.10186817 0.07684173
monitor 0.0111653873 0.05582694 0.15910677 0.05792045 0.37543615 0.09071877
plan 0.0009745006 0.02517460 0.29040117 0.01721618 0.06789021 0.14682475
synthesis 0.2346625767 0.03374233 0.46625767 0.04447853 0.06288344 0.07055215
monitor plan synthesis
adapt 0.03339882 0.01571709 0.000000000
cohesion 0.03303835 0.14100295 0.003539823
consensus 0.04661084 0.39579712 0.007584137
coregulate 0.08629442 0.23908629 0.018781726
discuss 0.02227284 0.01164262 0.140976968
emotion 0.03630596 0.09975326 0.002819880
monitor 0.01814375 0.21563154 0.016050244
plan 0.07552379 0.37420822 0.001786584
synthesis 0.01226994 0.07515337 0.000000000
Initial Probabilities :
adapt cohesion consensus coregulate discuss emotion monitor
0.0115 0.0605 0.2140 0.0190 0.1755 0.1515 0.1440
plan synthesis
0.2045 0.0195
We also prepare a group model for later comparison and permutation sections:
data("group_regulation_long")
prepared <- prepare_data(group_regulation_long,
action = "Action", actor = "Actor", time = "Time")
group_model <- group_tna(prepared, group = "Achiever")3 The Plot
One function. Zero arguments beyond the model. Publication-ready.
splot(model)When cograph receives a TNA object, it auto-applies TNA-appropriate defaults: oval layout, dark blue edges weighted by transition probability, donut rings showing initial state probabilities, and directional arrows. Every aspect of this plot can be customized — the rest of this tutorial shows how.
4 Node Styling
4.1 Shapes
Every node can have its own shape. cograph supports circles, squares, hexagons, diamonds, triangles, pentagons, stars, hearts, and more:
splot(model,
node_shape = c("circle", "square", "hexagon", "diamond", "triangle",
"pentagon", "star", "heart", "circle"),
layout = "circle"
)All Built-In Shapes
"circle","square","triangle","diamond","pentagon","hexagon","star","heart","ellipse","cross","rectangle","pie","donut"You can also register custom SVG shapes with
register_svg_shape()and use them by name.
4.2 Colors and Sizes
splot(model,
node_fill = palette_viridis(9),
node_border_color = "white",
node_border_width = 2,
node_size = c(12, 8, 10, 9, 7, 11, 8, 10, 9)
)4.3 Scaling Nodes by Centrality
scale_nodes_by maps a centrality measure to node size — larger nodes are more central. This is one of the most useful parameters for making network structure immediately visible:
par(mfrow = c(1, 2), mar = c(1, 1, 2, 1))
splot(model, scale_nodes_by = "betweenness", node_size_range = c(3, 14),
title = "Betweenness")
splot(model, scale_nodes_by = "strength", node_size_range = c(3, 14),
title = "Strength")scale_nodes_by Options
Any centrality measure name works:
"degree","strength","betweenness","closeness","eigenvector","pagerank","authority","hub","eccentricity","coreness","constraint","transitivity","harmonic","diffusion","leverage","kreach".For directional measures (in/out), use a list:
scale_nodes_by = list("strength", mode = "in").
Parameter Default Description scale_nodes_byNULLCentrality measure name or list with mode node_size_rangec(2, 8)c(min, max)size range for mapping
5 Edge Styling
5.1 Width and Curvature
splot(model,
edge_width = 3,
curvature = 0.3,
arrow_size = 0.015
)5.2 Edge Labels
Display transition probabilities directly on edges:
splot(model,
edge_labels = TRUE,
edge_label_size = 0.6,
edge_label_bg = "white",
threshold = 0.05
)5.3 Thresholding
Hide weak transitions to reveal the backbone:
par(mfrow = c(1, 2), mar = c(1, 1, 2, 1))
splot(model, title = "All edges")
splot(model, threshold = 0.10, title = "threshold = 0.10")Edge Parameters
Parameter Default Description edge_widthauto Base edge width edge_width_rangeauto c(min, max)width rangeedge_colorauto Edge color edge_alpha1Transparency edge_style"solid""solid","dashed","dotted"curvatureauto Curve amount arrow_sizeauto Arrowhead size threshold0Hide edges below this weight edge_labelsFALSEShow weight labels edge_label_style"plain""plain","ci","stars","full"edge_positive_colorauto Color for positive weights edge_negative_colorauto Color for negative weights
6 Donuts and Pies
Donut and pie node shapes encode additional quantitative information directly in the node rendering. In TNA, the donut ring is automatically filled from initial state probabilities — but you can use it for any node-level metric.
6.1 Donut Nodes
The default TNA plot already shows donut fills from model$inits. You can customize the appearance:
splot(model,
donut_color = "black",
donut_bg_color = "white",
donut_border_color = "black",
donut_shape = "circle"
)6.2 Pie Nodes
Pie chart nodes show a categorical distribution per node. You can combine them with the outer donut ring:
# Each node gets a 3-slice pie (e.g., from a clustering or condition split)
pie_vals <- list(
c(0.5, 0.3, 0.2), c(0.4, 0.4, 0.2), c(0.3, 0.3, 0.4),
c(0.6, 0.2, 0.2), c(0.2, 0.5, 0.3), c(0.4, 0.3, 0.3),
c(0.7, 0.2, 0.1), c(0.3, 0.4, 0.3), c(0.5, 0.2, 0.3)
)
splot(model,
pie_values = pie_vals,
pie_colors = c("#1976D2", "#FFA000", "#C62828"),
layout = "circle"
)Donut & Pie Parameters
Parameter Default Description donut_fillauto (from TNA inits) Numeric 0–1 per node donut_colorauto Fill color for the ring donut_inner_ratio0.5Inner radius ratio (0–1) donut_shape"circle""circle","square","hexagon","diamond","triangle","pentagon"donut_show_valueFALSEShow numeric value in center donut_value_suffix""Text after value (e.g., "%")pie_valuesNULLList of numeric vectors per node pie_colorsauto Colors for pie segments
7 Themes and Palettes
Themes apply a coordinated set of visual defaults with a single parameter:
par(mfrow = c(2, 2), mar = c(1, 1, 2, 1))
splot(model, theme = "classic", title = "classic")
splot(model, theme = "dark", title = "dark")
splot(model, theme = "minimal", title = "minimal")
splot(model, theme = "colorblind", title = "colorblind")
par(mfrow = c(1, 3), mar = c(1, 1, 2, 1))
splot(model, node_fill = palette_viridis(9), title = "viridis")
splot(model, node_fill = palette_pastel(9), title = "pastel")
splot(model, node_fill = palette_colorblind(9), title = "colorblind")All Themes
Theme Description "classic"Clean, professional (default) "dark"Dark background, light elements "minimal"Reduced chrome, subtle colors "colorblind"Colorblind-safe palette "grayscale"No color, grayscale only "vibrant"Bold, saturated colors "nature"Earth-tone palette "viridis"Viridis color scale Use
list_themes()to see all registered themes. Create custom themes withregister_theme().
8 Pipe-Friendly API (ggplot2)
The sn_* function family lets you build plots incrementally using the pipe operator. Each function takes a cograph_network and returns a modified one:
model |>
cograph(layout = "circle") |>
sn_theme("dark") |>
sn_nodes(size = 10, shape = "hexagon",
border_color = "white", border_width = 2) |>
sn_edges(width = 4, curvature = 0.2) |>
soplot(title = "Built with pipes")All sn_* Functions
Function What It Configures sn_layout(network, layout, seed)Layout algorithm sn_theme(network, theme)Visual theme sn_palette(network, palette, target, by)Color palette for nodes or edges sn_nodes(network, ...)All node aesthetics (size, shape, fill, labels, pie, donut) sn_edges(network, ...)All edge aesthetics (width, color, labels, curvature, CI) sn_render(network, ...)Render with grid graphics (alias for soplot())sn_ggplot(network, title)Convert to ggplot2 object sn_save(network, filename, ...)Save to file (PDF/PNG/SVG)
9 Specialized Plots
9.1 Chord Diagram
Chord diagrams show flows as curved ribbons around a circle. The width of each ribbon is proportional to the transition weight:
plot_chord(model, chord_alpha = 0.6, title = "Transition Flows")Chord Diagram Parameters
Parameter Default Description segment_colorsauto Colors for each node’s arc chord_color_by"source"Color ribbons by "source","target", or"weight"chord_alpha0.5Ribbon transparency self_loopFALSEShow self-transition arcs? threshold0Hide chords below this weight
9.2 Heatmap
The adjacency matrix as a color-coded grid, optionally grouped by cluster:
clusters <- list(
Social = c("discuss", "consensus", "cohesion", "emotion"),
Cognitive = c("plan", "monitor", "adapt", "synthesis"),
Meta = c("coregulate")
)
plot_heatmap(model,
cluster_list = clusters,
show_values = TRUE,
value_size = 3.5,
title = "Transition Heatmap"
)9.3 Alluvial / Sankey Diagram
Shows source-to-target flow as an alluvial diagram:
plot_alluvial(model$weights, threshold = 0.05)10 Group Comparisons
10.1 Multi-Group Grid
When you pass a group_tna object, splot renders all groups in a grid:
splot(group_model, i = NULL)10.2 Difference Network
plot_compare() computes and visualizes the element-wise difference between two groups. Green edges are stronger in the first group; red edges are stronger in the second:
plot_compare(group_model,
pos_color = "#2A9D8F",
neg_color = "#E76F51",
title = "High vs Low"
)11 Bootstrap
The bootstrap procedure determines which edges are stable properties of the data and which are sampling artifacts. Pass a tna_bootstrap object to splot() and it automatically styles significant vs. non-significant edges:
splot(boot)
splot(boot, display = "significant")
splot(boot, display = "ci", threshold = 0.05)Bootstrap Display Modes
displayDescription "styled"(default)Significant = solid, non-significant = dashed "significant"Only significant edges shown "full"All edges shown with equal styling "ci"Confidence interval labels on edges Additional parameters:
show_stars(significance stars),show_ci(CI values),width_by(scale width by weight or CI width).
12 Permutation Testing
Permutation testing evaluates whether between-group differences are statistically significant. Pass the result to splot() to visualize which edge differences survived the permutation test:
set.seed(265)
perm <- permutation_test(group_model, iter = 1000)
splot(perm)Permutation Plot Parameters
Parameter Default Description show_nonsigTRUEShow non-significant edges? edge_nonsig_color"gray80"Color for non-significant edges edge_nonsig_alpha0.3Transparency for non-significant edges show_starsFALSEShow significance stars show_effectFALSEShow effect sizes
13 Mixed Networks
Overlay directed and undirected edges on the same plot. This is useful when you have two types of relationships — for example, symmetric co-occurrence patterns and asymmetric transition probabilities. The defaults follow TNA conventions: oval layout, weight-scaled edges, dotted edge starts, and visible edge labels.
# Undirected: symmetric co-occurrence (half the weight for visual balance)
set.seed(42)
sym <- (model$weights + t(model$weights)) / 2
noise <- matrix(runif(length(sym), -0.02, 0.02), nrow = nrow(sym))
noise <- (noise + t(noise)) / 2 # keep symmetric
diag(noise) <- 0
sym <- pmax(sym * 0.5 + noise, 0)
# Directed: original transition probabilities
asym <- model$weights
# Defaults: oval layout, blue edges, weight-scaled widths, edge labels
plot_mixed_network(sym, asym, threshold = 0.03,
title = "Co-occurrence + Transitions"
)You can distinguish edge types by color: undirected co-occurrence edges are straight steel-blue lines, while directed transitions are curved dark-blue arrows with dotted starts. Edge widths scale automatically by weight, so stronger connections stand out.
plot_mixed_network(sym, asym, threshold = 0.03,
sym_color = "#E63946",
asym_color = "#003355",
title = "Red undirected + Blue directed"
)14 Multi-Cluster Visualization
plot_mcml() draws two layers simultaneously: individual nodes inside cluster shells (bottom) and aggregated cluster-level summary pies (top).
clusters <- list(
Social = c("discuss", "consensus", "cohesion", "emotion"),
Cognitive = c("plan", "monitor", "adapt", "synthesis"),
Meta = c("coregulate")
)
mat <- model$weights
plot_mcml(mat, clusters, mode = "tna",
title = "Collaborative Regulation Clusters")plot_mtna() provides a flat single-layer alternative:
plot_mtna(mat, clusters, show_labels = TRUE, label_abbrev = 4,
title = "Flat Cluster Map")15 Nestimate Integration
The Nestimate package estimates networks from sequence data using multiple methods. Its objects dispatch through splot() automatically.
library(Nestimate)
net <- build_network(group_regulation, method = "relative")
splot(net, title = "Nestimate: relative transitions")Bootstrap stability:
set.seed(42)
nboot <- bootstrap_network(net, iter = 500)
splot(nboot, title = "Nestimate bootstrap")Group comparison:
grp <- build_network(group_regulation_long, method = "relative",
action = "Action", actor = "Actor", time = "Time",
group = "Achiever")
splot(grp)16 Motifs
Motif analysis identifies recurring local connectivity patterns — the building blocks of network structure. cograph uses the MAN (Mutual, Asymmetric, Null) classification for directed triads: 16 possible patterns from empty (003) to fully connected (300).
16.1 Census
motifs() counts how often each MAN type appears and tests significance against a configuration model null:
mot <- motifs(model, significance = TRUE, n_perm = 100, seed = 42)
motMotif Census
Level: individual | 2000 units | States: 9 | Pattern: triangle
Significance: permutation (n_perm=100)
Type distribution:
030C 030T 120C 120D 120U 210 300
1 1 1 1 1 1 1
Top 7 results:
type count expected z p sig
030T 620 435.4 8.14 0.0000 TRUE
300 79 169.4 -7.44 0.0000 TRUE
210 581 766.4 -6.52 0.0000 TRUE
120C 1482 1356.5 3.40 0.0007 TRUE
120U 190 169.7 1.55 0.1211 FALSE
120D 178 172.9 0.46 0.6455 FALSE
030C 1054 1063.6 -0.29 0.7718 FALSE
16.2 Visualization
plot(mot, type = "significance")
plot(mot, type = "patterns")16.3 Named subgraphs
subgraphs() identifies the specific node triples forming each pattern — which states participate in which motifs:
sg <- subgraphs(model, significance = TRUE, top = 10, pattern="triangle")
sgMotif Subgraphs
Level: individual | 2000 units | States: 9 | Pattern: triangle
Significance: permutation (n_perm=1000)
Min count: > 5
Type distribution:
120C 030C 030T 210
6 2 1 1
Top 10 results:
triad observed type expected z p sig
consensus - discuss - plan 278 210 1504.1 -148.31 0 TRUE
cohesion - consensus - plan 151 120C 1387.4 -109.94 0 TRUE
consensus - emotion - plan 436 120C 1443.3 -105.12 0 TRUE
discuss - emotion - plan 76 030T 1317.2 -103.78 0 TRUE
consensus - coregulate - plan 301 120C 1395.1 -100.86 0 TRUE
consensus - monitor - plan 187 120C 1336.8 -99.64 0 TRUE
consensus - plan - synthesis 13 120C 1275.7 -99.07 0 TRUE
consensus - discuss - emotion 238 120C 1346.5 -96.38 0 TRUE
cohesion - discuss - plan 18 030C 1199.3 -88.93 0 TRUE
coregulate - discuss - plan 61 030C 1217.0 -88.68 0 TRUE
plot(sg, n = 10, ncol = 5)17 Higher-Order Networks
Standard TNA assumes first-order Markov dynamics: the next state depends only on the current state. Higher-order analysis tests whether longer sequential memory exists — cases where what happened two or three steps ago changes the transition probabilities.
17.1 Model order selection
build_mogen() fits Markov models at increasing orders and selects the best via AIC/BIC:
data("human_cat", package = "Nestimate")
hon_net <- build_network(human_cat, method = "tna",
action = "category", time = "timestamp", session = "session_id")
mg <- build_mogen(hon_net, max_order = 4)
summary(mg)Multi-Order Generative Model (MOGen) Summary
States: Command, Correct, Frustrate, Inquire, Interrupt, Refine, Request, Specify, Verify
Paths: 1202 | Observations: 10563
order layer_dof cum_dof loglik aic bic selected
0 8 8 -21525.46 43066.91 43125.03
1 72 80 -20328.25 40816.51 41397.71
2 605 685 -19334.29 40038.58 45015.18
3 2061 2746 -16859.93 39211.85 59161.85
4 2039 4785 -12426.49 34422.97 69186.54 <--
Optimal order: 4 (by aic)
plot(mg)17.2 Higher-Order Network (HON)
HON detects where in the state space higher-order dependencies exist — which transitions change depending on prior context:
hon <- build_hon(hon_net)
honHigher-Order Network (HON)
Nodes: 903 (9 first-order states)
Edges: 3324
Max order: 5 (requested 5)
Min freq: 1
Trajectories: 1202
17.3 Path anomaly detection (HYPA)
HYPA identifies paths that occur significantly more or less often than expected under a hypergeometric null model:
hypa <- build_hypa(hon_net)
table(hypa$scores$anomaly)
normal over under
2552 110 71
Over-represented paths — learned behavioral routines:
path
56 Command -> Command -> Correct -> Command
97 Request -> Command -> Correct -> Specify
98 Specify -> Command -> Correct -> Specify
158 Command -> Command -> Inquire -> Inquire
162 Specify -> Command -> Inquire -> Inquire
186 Interrupt -> Command -> Interrupt -> Command
from to observed expected ratio
56 Command -> Command -> Correct Command 11 5.915291 1.859587
97 Request -> Command -> Correct Specify 5 1.394763 3.584838
98 Specify -> Command -> Correct Specify 8 4.358635 1.835437
158 Command -> Command -> Inquire Inquire 6 2.129505 2.817557
162 Specify -> Command -> Inquire Inquire 5 2.366116 2.113167
186 Interrupt -> Command -> Interrupt Command 7 1.481936 4.723551
hypa_score anomaly
56 0.9821252 over
97 0.9969815 over
98 0.9664514 over
158 0.9938890 over
162 0.9668313 over
186 0.9998559 over
17.4 Simplicial visualization
plot_simplicial() renders higher-order pathways as smooth blobs over the network. Source states appear in blue, target states in red.
plot_simplicial(hon_net, max_pathways = 6,
title = "Higher-Order Pathways")Dismantled view — one panel per pathway:
plot_simplicial(hon_net, max_pathways = 9,
dismantled = TRUE, ncol = 3)HYPA anomalous pathways:
plot_simplicial(hon_net, method = "hypa", max_pathways = 9,
dismantled = TRUE, ncol = 3)18 Robustness
robustness() simulates network degradation under targeted and random node/edge removal:
plot_robustness(x = mat,
measures = c("betweenness", "degree", "random"),
n_iter = 50, seed = 42,
title = "Node Removal Robustness")19 Saving and Exporting
19.1 Direct File Output
19.2 Pipe-Friendly Saving
19.3 ggplot2 Export
20 Complete Parameter Reference
splot() — All Parameters
Input & Layout:
x,layout,directed,seed,theme,title,background,groupsNode Appearance:
node_size,node_size2,node_shape,node_fill,node_border_color,node_border_width,node_alpha,scale_nodes_by,node_size_rangeLabels:
labels,label_size,label_color,label_position,label_fontface,label_fontfamilyDonut:
donut_fill,donut_color,donut_inner_ratio,donut_shape,donut_show_value,donut_value_suffix,donut_value_digits,donut2_values,donut2_colorsPie:
pie_values,pie_colorsEdge Appearance:
edge_color,edge_width,edge_size,edge_width_range,edge_scale_mode,edge_cutoff,edge_alpha,edge_style,edge_start_style,edge_positive_color,edge_negative_color,curvature,curvesArrows:
arrow_size,arrow_angle,show_arrows,bidirectionalEdge Labels:
edge_labels,edge_label_size,edge_label_color,edge_label_bg,edge_label_position,edge_label_offset,edge_label_fontface,edge_label_style,edge_label_templateCI Underlay:
edge_ci,edge_ci_scale,edge_ci_alpha,edge_ci_color,edge_ci_styleWeight / Threshold:
threshold,minimum,maximum,weight_digitsLayout Control:
rescale,layout_scale,layout_margin,aspect,loop_rotationFile Output:
filename,filetype,width,height,resTNA:
i(group index for group_tna)
All Available Layouts
Built-in:
"oval"(default),"circle","spring","grid","random","star","bipartite","groups"igraph (two-letter codes):
"kk"(Kamada-Kawai),"fr"(Fruchterman-Reingold),"drl","mds","lgl","ni"(nicely),"tr"(tree),"st"(Sugiyama)You can also pass a custom layout matrix (2-column matrix with x, y coordinates) or a layout function.
References
cograph: Modern R Package for Network Visualization. https://github.com/sonsoleslp/cograph
Saqr, M., López-Pernas, S., Törmänen, T., Kaliisa, R., Misiejuk, K., & Tikka, S. (2025). Transition Network Analysis: A Novel Framework for Modeling, Visualizing, and Identifying the Temporal Patterns of Learners and Learning Processes. In Proceedings of the 15th International Learning Analytics and Knowledge Conference (LAK ’25) (pp. 351–361). ACM. https://doi.org/10.1145/3706468.3706513
Tikka, S., López-Pernas, S., & Saqr, M. (2025). tna: An R Package for Transition Network Analysis. Applied Psychological Measurement. https://doi.org/10.1177/01466216251348840