Skip to contents

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)

  1. Layer-specific style (highest): add_points(color = "red")
  2. Plot theme override: plot(theme = list(palette = "plasma"))
  3. Instance theme: vis$set_theme(vistool_theme(...))
  4. 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
  }
)

Development Workflow

1. Setup Development Environment

git clone https://github.com/slds-lmu/vistool.git
cd vistool
devtools::load_all()
devtools::install_deps(dependencies = TRUE)

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.md

4. 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 check

You can also check the rendered website:

pkgdown::build_site()

Guidelines

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 requiring Python dependencies on CI
skip_on_ci()

# Skip if optional package not available  
skip_if_not_installed("plotly")

Documentation with roxygen2

  • Use roxygen2 tags (@title, @description, @param, @return, @examples)
  • Include working examples
  • Document all parameters
  • Use consistent style with existing code
  • For reusable documentation blocks, use templates in man-roxygen/ directory.