Skip to contents

Module Architecture

modFrames follows a hierarchical module pattern with a main entry point that delegates to specialized sub-modules.

Structure Overview

modFramesUI() / modFramesServer()
    |
    ├── playTimelapseUI() / playTimelapseServer()
    │   └── Timelapse playback with animation controls
    │
    └── compareFramesUI() / compareFramesServer()
        ├── Journey Story Card (storytelling insights)
        ├── Frame Comparison Card (A/B comparison + diff)
        └── generateJourneyInsights() / plotCreativeJourney()

Main Module: modFrames

modFramesUI()

The main UI function assembles the complete interface:

  1. CSS Stylesheets: inst/css/modFrames-inline.css and inst/css/modFrames-styles.css
  2. JavaScript: inst/js/modFrames-tooltips.js for Bootstrap 5 tooltips
  3. Slider Skin: Custom shinyWidgets slider styling
  4. Dark Mode Toggle: Fixed-position light/dark switch (bslib)
  5. Sub-Module UIs: Timelapse controls and comparison cards
modFramesUI <- function(id) {
  ns <- shiny::NS(id)

  shiny::tagList(
    shiny::includeCSS(system.file("css/modFrames-inline.css", package = "modFrames")),
    shiny::includeCSS(system.file("css/modFrames-styles.css", package = "modFrames")),
    shiny::includeScript(system.file("js/modFrames-tooltips.js", package = "modFrames")),
    # ... dark mode toggle ...
    playTimelapseUI(ns("timelapse")),
    compareFramesUI(ns("compframes"))
  )
}

modFramesServer()

The main server function is minimal - it simply initializes sub-modules:

modFramesServer <- function(id, r) {
  shiny::moduleServer(id, function(input, output, session) {
    playTimelapseServer("timelapse", r)
    compareFramesServer("compframes", r)
  })
}

Sub-Module: playTimelapse

Handles animated timelapse playback of artwork creation.

UI Components

  • Slider with Animation: Frame selector with play/pause buttons
  • Metrics Row: Total strokes, elapsed minutes, unique colors
  • Frame Display: Current frame image from CDN
  • Color Graph: Per-frame color composition visualization

Server Logic

  1. Extracts UUIDs from r$appdata$artwork$stats
  2. Reads frame_stats for metric calculations
  3. Computes cumulative unique colors using colors_added
  4. Renders frame images via artutils::path_replay_frame()
  5. Renders color graphs via artutils::path_replay_graph()

Key Reactives

r_artist <- reactive(req(r$appdata$artwork$stats$artist_uuid))
r_artwork <- reactive(req(r$appdata$artwork$stats$art_uuid))
r_nframes <- reactive(req(r$appdata$config$n_frames))
r_frame_stats <- reactive(req(r$appdata$artwork$frame_stats))

Sub-Module: compareFrames

Handles side-by-side frame comparison with difference visualization and storytelling insights.

UI Components

  1. Journey Summary Card:
    • Journey insights text (left column, 7/12 width)
    • Plotly visualization (right column, 5/12 width)
  2. Frame Comparison Card:
    • Dual-handle slider for selecting Frame A and Frame B
    • Contextual insights (alerts for phase changes, color expansions)
    • Three-column display: Frame A, Frame B, Difference

Server Logic

  1. Journey Insights: Generates once when data loads

    r_journey_insights <- reactive({
      dt <- r_frame_stats()
      generateJourneyInsights(dt)
    })
  2. Frame Selection: Updates slider range based on total frames

  3. Frame Rendering: Fetches images from CDN for A and B

  4. Difference Image: Creates via ImageMagick comparison

    write_diff <- function(img_a, img_b, outpath) {
      magick::image_compare(img_b, img_a, fuzz = 5) |>
     magick::image_transparent("#cccccc", fuzz = 5) |>
     magick::image_write(outpath, format = "png")
    }
  5. Contextual Insights: Dynamic alerts based on frame selection

    • Major color expansion (>50 colors added)
    • Phase transitions
    • Peak creative intensity (palette_change_score > 0.7)
    • Sustained effort (>1 hour between frames)

Data Flow

artutils::get_appdata()
    |
    v
r$appdata$artwork$frame_stats (21 columns)
    |
    +---> playTimelapseServer
    |       - Reads elapsed_minutes, cumulative_strokes, unique_colors
    |       - Computes cumulative_colors from colors_added
    |
    +---> compareFramesServer
            |
            +---> generateJourneyInsights(frame_stats)
            |       - Analyzes color approach, phases, complexity
            |       - Returns structured insights object
            |
            +---> plotCreativeJourney(frame_stats, insights)
                    - Creates Plotly visualization
                    - Shows color evolution with phase boundaries

Helper Functions

info_icon_tooltip()

Creates Bootstrap 5 tooltip icons for contextual help:

info_icon_tooltip <- function(tooltip_text, placement = "top") {
  icon_element <- shiny::icon("info-circle", class = "mod-frames-info-icon")
  icon_element$attribs$`data-bs-toggle` <- "tooltip"
  icon_element$attribs$title <- tooltip_text
  icon_element
}

metric_block()

Renders consistent metric displays with optional icons:

metric_block <- function(value, label, icon = NULL, highlight = FALSE) {
  # Supports both static values and Shiny outputs (textOutput)
  # Returns styled div with icon, value, and label
}

Styling

CSS Files

  • inst/css/modFrames-inline.css: Inline critical styles
  • inst/css/modFrames-styles.css: Component styles

CSS Classes

Class Purpose
.mod-frames-card Base card styling
.mod-frames-journey-card Journey summary card
.mod-frames-compare-card Frame comparison card
.mod-frames-timelapse-card Timelapse controls
.mod-frames-frame-card Individual frame display
.mod-frames-metrics-row Horizontal metrics layout
.mod-frames-metric Individual metric block
.mod-frames-info-icon Tooltip info icons

Extension Points

Adding New Metrics

To display additional frame_stats columns:

  1. Access the column in the relevant server function
  2. Add a metric_block() call in the UI render function
  3. Style with existing CSS classes

Custom Visualizations

To add new Plotly charts:

  1. Create a new plotlyOutput() in the UI
  2. Render using r_frame_stats() reactive
  3. Follow the pattern in plotCreativeJourney()

Next Steps