Developer reference
Essential commands
# Load package for development
devtools::load_all()
# Install all dependencies
devtools::install_deps(dependencies = TRUE)
# Check package (like R CMD check)
devtools::check()
# Run all tests
devtools::test()
# Run specific test file
devtools::test_active_file('tests/testthat/test-plot-customization.R')
# Install package locally
devtools::install()
# Build source tarball
devtools::build()
# Generate function documentation from roxygen2 comments
devtools::document()
# Build README.md from README.Rmd
devtools::build_readme()
# Build all vignettes
devtools::build_vignettes()
# Build a specific vignette
devtools::load_all(); rmarkdown::render('vignettes/loss_functions.Rmd')
# Build package website
pkgdown::build_site()
# Preview website locally
pkgdown::preview_site()Structure
vistool/
├── DESCRIPTION # Package metadata, dependencies
├── NAMESPACE # Exports (auto-generated by roxygen2)
├── README.Rmd # Source for GitHub README
├── NEWS.md # Changelog
├── CONTRIBUTING.md # This file
├── R/ # R source code
│ ├── as_visualizer.r # main user interface
│ ├── LossFunction.R # Loss functions
│ ├── Objective.R # Objective function classes
│ ├── Optimizer.R # Optimization algorithms
│ ├── Visualizer*.R # Visualization classes
│ └── zzz.R # Package imports
├── man/ # Documentation (auto-generated)
├── man-roxygen/ # Roxygen2 templates
├── tests/testthat/ # Test files
├── vignettes/ # Long-form documentation
└── .github/workflows/ # CI/CD
Core architecture (visualization)
Visualizer
├── VisualizerModel # Handles both 1D and 2D
├── VisualizerObj # Handles both 1D and 2D
├── VisualizerLossFuns
└── VisualizerSurface # plotly-based
├── VisualizerSurfaceModel
└── VisualizerSurfaceObj
-
Visualizer: Base visualization class-
VisualizerLossFuns: Plotting loss functions with ggplot2 -
VisualizerModel: Plotting model predictions with ggplot2 -
VisualizerObj: Plotting objective functions with ggplot2 -
VisualizerSurface*: Interactive surface plotting with plotly for 2D functions only
-
-
as_visualizer(): Smart constructor that selects appropriate visualizer
API design principles
Constructor vs. styling separation
Constructors (as_visualizer(), VisualizerModel$new(), etc.) accept only: - Domain objects: tasks, learners, objectives, loss functions - Computational parameters: limits, padding, n_points, grids
Styling is handled separately via: - Instance theme: vis$set_theme(vistool_theme(...)) - Plot theme: vis$plot(theme = list(...), ...) - Layer styling: vis$add_points(color = "red", size = 3)
Example workflow
# 1. Create visualizer (computational setup only)
vis <- as_visualizer(task, learner = learner, n_points = 200, padding = 0.1)
# 2. Set styling preferences
vis$set_theme(vistool_theme(palette = "plasma", text_size = 14))
# 3. Add layers with auto colors
vis$add_training_data(color = "auto") # Uses theme palette
vis$add_boundary(color = "red") # Explicit override
# 4. Plot with optional theme override
p <- vis$plot(theme = list(alpha = 0.7))
# 5. Save (uses cached plot for efficiency)
vis$save("plot.png")Theme system
vistool uses a flexible theme system that separates “what to plot” from “how to plot”:
Theme hierarchy (precedence)
-
Layer-specific style (highest):
add_points(color = "red") -
Plot theme override:
plot(theme = list(palette = "plasma")) -
Instance theme:
vis$set_theme(vistool_theme(...)) -
Package default (lowest): Set via
options(vistool.theme = ...)
Theme usage
# Set instance theme
vis$set_theme(vistool_theme(palette = "plasma", text_size = 14, alpha = 0.7))
# Override theme for single plot
vis$plot(theme = list(text_size = 12, theme = "bw"))
# Layer-specific styling (highest precedence)
vis$add_points(data, color = "red", size = 3)Deferred rendering
All visualizer classes in vistool follow a standardized plot() method structure that leverages the deferred rendering architecture. This ensures consistent behavior, proper color resolution, and maintainable code.
Standard pattern
1. Visualizer.R
The base Visualizer class provides: - Layer storage: private$store_layer(type, spec) - Layer retrieval: private$get_layers_by_type(type) - Theme management: Effective theme resolution in plot() method - Color resolution: Auto colors resolved using effective theme palette
CAREFUL: Its plot() method needs to be called by all child classes (VisualizerSurface, VisualizerModel, …) to resolve effective theme and prepare rendering.
2. Child classes
These extend the plot() method from the base Visualizer class and add methods specific for that type (e.g., add_contours() for all VisualizerSurface classes, add_optimization_trace for Visualizer*Obj), they inherit from Visualizer or VisualizerSurface.
VisualizerLossFuns, VisualizerModel, VisualizerObj, VisualizerSurfaceModel, VisualizerSurfaceObj should all follow this pattern:
plot = function(...) {
# 1. ALWAYS call parent first to resolve effective theme and setup
p <- super$plot(...) # or just super$plot(...) for plotly classes
# 2. Render class-specific layers using dedicated private methods
private$render_layer_type_1()
private$render_layer_type_2()
# ... etc
# 3. Resolve auto colors using effective theme
self$resolve_layer_colors()
# 4. Cache and return the plot object (for ggplot2 classes only)
private$.last_plot = p # Cache for save() method
return(p) # omit for plotly classes that modify private$.plot
}Implementation examples
ggplot2-based classes (1D, 2D)
plot = function(...) {
# Call parent to resolve effective theme and prepare rendering
p <- super$plot(...)
# render layers in the order they were added
if (!is.null(private$.layers_to_add)) {
for (layer in private$.layers_to_add) {
if (layer$type == "optimization_trace") {
p = private$render_optimization_trace_layer(p, layer$spec)
} else if (layer$type == "layer_type_2") {
# render layer_type_2
}
# ... handle other layer types
}
}
# Resolve auto colors using effective theme
self$resolve_layer_colors()
# Cache and return plot
private$.last_plot = p
return(p)
}
private = list(
render_optimization_trace_layers = function(plot_obj) {
trace_layers <- private$get_layers_by_type("optimization_trace")
if (length(trace_layers) == 0) {
return(plot_obj)
}
for (trace_spec in trace_layers) {
plot_obj <- private$render_optimization_trace_layer(plot_obj, trace_spec)
}
return(plot_obj)
}
)plotly-based classes (surface)
# Good Example: VisualizerSurfaceObj
plot = function(...) {
# Call parent to resolve effective theme
super$plot(...)
# Render stored layers (modifies private$.plot directly)
private$render_optimization_trace_layers()
private$render_taylor_layers()
private$render_hessian_layers()
# Resolve auto colors using effective theme
self$resolve_layer_colors()
# Cache and return plot
private$.last_plot = private$.plot
return(private$.plot)
}
private = list(
render_optimization_trace_layers = function() {
trace_layers <- private$get_layers_by_type("optimization_trace")
# ... process layers, modify private$.plot directly
# Colors are resolved using private$.effective_theme$palette
}
)LaTeX handling
Run plot-bound strings through private$format_label() rather than manual TeX glue. The helper honours plot(latex = ...) preferences, layer-level overrides such as annotations_latex, and manages both latex2exp (ggplot2 expressions) and MathJax configuration for plotly traces. When you add labels to data frames, wrap expression vectors with I(list(...)) so ggplot2 keeps them list-valued.
Development workflow
2. Make changes
Edit files in R/, add roxygen2 documentation:
#' @title Brief description
#' @description Detailed description
#' @param x Parameter description
#' @return Return value description
#' @examples
#' # Example code
#' @export
my_function = function(x) {
# Implementation
}For reusable documentation blocks, consider using templates from man-roxygen/ directory.
3. Update documentation
devtools::document() # Generate man/ files
devtools::build_readme() # Update README.md4. Add tests
Test all public functions using testthat:
test_that("my feature works", {
result = my_function(input)
expect_equal(result, expected)
})5. Check package
devtools::test() # Run tests
devtools::check() # Full package checkYou can also check the rendered website:
pkgdown::build_site()Guidelines
Naming conventions
- Classes (R6 generators and class-like objects): CamelCase
- Examples:
Visualizer,VisualizerModel,Objective,OptimizerGD
- Examples:
- Everything else: snake_case
- Functions:
as_visualizer(),merge_theme(),get_vistool_color() - Variables/fields/args:
n_points,x_limits,effective_theme - Files: snake_case where feasible (e.g.,
as_visualizer.R); class files should retain CamelCase to match class names (e.g.,VisualizerModel.R).
- Functions:
Tooling:
- lintr:
.lintrconfig allows bothsnake_caseandCamelCase. It cannot distinguish classes from other objects, so we enforce “CamelCase only for classes” via code review and PRs. If you must use a non-conforming name due to external APIs, add a short comment and, if necessary, a targeted# nolint: object_name_linter.on that line. - styler: formatting only; it does not rename identifiers. See
.stylerfor formatting configuration.
Documentation titles should use Sentence case.
Testing
- Each R file should have corresponding test file
- Use descriptive test names:
test_that("objective evaluation works", ...) - Test both success and failure cases
- Use appropriate
expect_*()functions
# Skip tests on CI
skip_on_ci()
# Skip if optional package not available
skip_if_not_installed("plotly")