2026-02-03
This article demonstrates how to visualize the structure and dependencies of package functions using the foodwebr package. Understanding function dependencies helps developers:
The foodwebr package creates dependency graphs showing which functions call which other functions. These visualizations are particularly useful for:
If you don’t have foodwebr installed, you can install it from CRAN:
Or from GitHub for the latest development version:
The foodweb() function analyzes function dependencies. When examining a package, you typically want to look at specific functions or the entire package namespace:
library(foodwebr)
# Create a foodweb for the example_function
fw <- foodweb(FUN = example_function)
fw
#> # A `foodweb`: 6 vertices and 7 edges
#> digraph 'foodweb' {
#> example_function() -> { calculate_statistic(), format_result() }
#> calculate_summary() -> { example_function(), format_result(), clean_data() }
#> calculate_statistic() -> { clean_data() }
#> format_result()
#> clean_data() -> { validate_input() }
#> validate_input()
#> }The plot() method creates an interactive visualization using DiagrammeR:
Understanding the Graph
By default, foodwebr filters to show only functions directly related to the specified function. You can control this behavior:
To see all exported and internal functions in the package:
# Get the package namespace environment
pkg_env <- asNamespace("rpt")
# Create foodweb for entire package
fw_pkg <- foodweb(env = pkg_env)
fw_pkg
#> # A `foodweb`: 6 vertices and 7 edges
#> digraph 'foodweb' {
#> calculate_statistic() -> { clean_data() }
#> calculate_summary() -> { clean_data(), example_function(), format_result() }
#> clean_data() -> { validate_input() }
#> example_function() -> { calculate_statistic(), format_result() }
#> format_result()
#> validate_input()
#> }From the dependency graph, you can identify different types of functions:
For our package:
# Convert to text representation for inspection
cat(as.character(fw_pkg))
#> digraph 'foodweb' {
#> "calculate_statistic()" -> { "clean_data()" }
#> "calculate_summary()" -> { "clean_data()", "example_function()", "format_result()" }
#> "clean_data()" -> { "validate_input()" }
#> "example_function()" -> { "calculate_statistic()", "format_result()" }
#> "format_result()"
#> "validate_input()"
#> }Dependency Patterns
In well-designed packages, you typically see:
In the rpt package, we can see this pattern:
validate_input() is a low-level helper (no package dependencies)clean_data() calls validate_input()calculate_statistic() calls clean_data()format_result() is a low-level formatter (no package dependencies)example_function() is a high-level API that calls calculate_statistic() and format_result()calculate_summary() is another high-level API that calls clean_data() and example_function()This creates a clear layered architecture with well-separated concerns.
Let’s examine the actual structure of the rpt package:
# List all functions in the package
pkg_functions <- ls(asNamespace("rpt"))
cat("Package functions:\n")
#> Package functions:
cat(paste("-", sort(pkg_functions), collapse = "\n"))
#> - calculate_statistic
#> - calculate_summary
#> - clean_data
#> - example_function
#> - format_result
#> - validate_inputThe dependency relationships are:
example_function(): Calls calculate_statistic() → format_result()calculate_summary(): Calls clean_data() → example_function()validate_input(): No package dependencies (leaf function)clean_data(): Calls validate_input()calculate_statistic(): Calls clean_data()format_result(): No package dependencies (leaf function)This architecture demonstrates good separation of concerns with clear data flow from validation through cleaning to calculation and formatting.
The tidygraph package provides powerful tools for graph analysis. You can convert a foodweb object to work with these tools:
if (requireNamespace("tidygraph", quietly = TRUE)) {
tg <- tidygraph::as_tbl_graph(fw_pkg)
print(tg)
}
#> # A tbl_graph: 6 nodes and 7 edges
#> #
#> # A directed acyclic simple graph with 1 component
#> #
#> # Node Data: 6 × 1 (active)
#> name
#> <chr>
#> 1 calculate_statistic
#> 2 calculate_summary
#> 3 clean_data
#> 4 example_function
#> 5 format_result
#> 6 validate_input
#> #
#> # Edge Data: 7 × 2
#> from to
#> <int> <int>
#> 1 1 3
#> 2 2 3
#> 3 2 4
#> # ℹ 4 more rowsYou can pass additional arguments to customize the graph appearance:
Get the graphviz DOT representation:
# As text
foodweb_text <- foodweb(env = asNamespace("rpt"), as.text = TRUE)
cat(foodweb_text)
#> digraph 'foodweb' {
#> "calculate_statistic()" -> { "clean_data()" }
#> "calculate_summary()" -> { "clean_data()", "example_function()", "format_result()" }
#> "clean_data()" -> { "validate_input()" }
#> "example_function()" -> { "calculate_statistic()", "format_result()" }
#> "format_result()"
#> "validate_input()"
#> }Use dependency graphs during code review to:
Before refactoring:
Include dependency graphs in:
Keeping Documentation Updated
When adding new functions or modifying dependencies, regenerate the foodweb graphs to keep documentation current. Consider adding automated checks in your CI/CD pipeline.
You can focus on specific functions to understand their dependencies:
These focused views help understand:
When designing package architecture:
foodwebr graphs in your documentationThis article demonstrated how to use foodwebr to visualize and understand package function dependencies. Key takeaways:
foodwebr creates interactive dependency graphstidygraph enables advanced graph analysisThe rpt package demonstrates a clean layered architecture with: - Low-level utility functions (validate_input(), format_result()) - Mid-level processing functions (clean_data(), calculate_statistic()) - High-level API functions (example_function(), calculate_summary())
This structure makes the code easier to understand, test, and maintain.