Practical Examples: Before and After Dashboard Transformations

library(bidux)
library(shiny)
library(bslib)
library(dplyr)

Introduction: Real Dashboard Problems, Real Solutions

This vignette walks through common dashboard scenarios that data scientists and Shiny developers encounter, showing how the BID framework transforms user experience while maintaining analytical rigor.

Each example includes:

Example 1: The “Everything Dashboard” Problem

The Scenario

You’re a data scientist at a SaaS company. Your stakeholders asked for “a dashboard that shows everything about user engagement.” Being thorough, you built exactly that.

Before: Technical Excellence, User Confusion

# The "show everything" approach
ui_before <- navbarPage(
  "User Engagement Analytics",

  tabPanel(
    "Overview",
    fluidRow(
      # 12 KPIs across the top
      column(2, valueBoxOutput("dau")),
      column(2, valueBoxOutput("wau")),
      column(2, valueBoxOutput("mau")),
      column(2, valueBoxOutput("retention")),
      column(2, valueBoxOutput("churn")),
      column(2, valueBoxOutput("ltv"))
    ),
    fluidRow(
      column(2, valueBoxOutput("sessions")),
      column(2, valueBoxOutput("session_duration")),
      column(2, valueBoxOutput("pages_per_session")),
      column(2, valueBoxOutput("bounce_rate")),
      column(2, valueBoxOutput("conversion")),
      column(2, valueBoxOutput("revenue"))
    ),

    # Multiple complex charts
    fluidRow(
      column(6, plotOutput("engagement_trend", height = "400px")),
      column(6, plotOutput("cohort_analysis", height = "400px"))
    ),
    fluidRow(
      column(4, plotOutput("funnel_chart")),
      column(4, plotOutput("retention_curve")),
      column(4, plotOutput("ltv_distribution"))
    )
  ),

  tabPanel("Segments", "More detailed segmentation..."),
  tabPanel("Cohorts", "Cohort analysis details..."),
  tabPanel("Funnels", "Conversion funnel details..."),
  tabPanel("Revenue", "Revenue analytics..."),
  tabPanel("Product", "Product usage analytics...")
)

The Problem: Cognitive Overload

User feedback: “I can’t find what I’m looking for” and “It’s overwhelming”

What’s happening: - 12+ metrics compete for attention simultaneously - No clear hierarchy of importance - Users don’t know where to look first - Analysis paralysis from too many options

BID Framework Solution

Let’s apply the systematic BID approach:

# Stage 1: Interpret - Understand the real user need
interpret_result <- bid_interpret(
  central_question = "How is our user engagement trending, and what needs attention?",
  data_story = list(
    hook = "User engagement metrics are spread across multiple systems",
    context = "Leadership needs quick insights for weekly business reviews",
    tension = "Current dashboards take too long to interpret",
    resolution = "Provide immediate key insights with drill-down capability"
  ),
  user_personas = list(
    list(
      name = "Sarah (Product Manager)",
      goals = "Quickly spot concerning trends and dive deeper when needed",
      pain_points = "Too many metrics to process in limited meeting time",
      technical_level = "Intermediate"
    ),
    list(
      name = "Mike (Executive)",
      goals = "Understand overall health at a glance",
      pain_points = "Gets lost in details when just needs the big picture",
      technical_level = "Basic"
    )
  )
)

# Stage 2: Notice - Identify the specific problem
notice_result <- bid_notice(
  previous_stage = interpret_result,
  problem = "Users experience information overload with 12+ simultaneous metrics",
  evidence = "User interviews show 80% struggle to prioritize information, average time-to-insight is 5+ minutes"
)

# Stage 3: Anticipate - Consider cognitive biases
anticipate_result <- bid_anticipate(
  previous_stage = notice_result,
  bias_mitigations = list(
    attention_bias = "Use size and color to direct focus to most important metrics first",
    choice_overload = "Implement progressive disclosure - show key metrics, hide advanced analytics until requested",
    anchoring = "Lead with the most important business metric to set proper context"
  )
)

# Stage 4: Structure - Organize for cognitive efficiency
structure_result <- bid_structure(previous_stage = anticipate_result)

# Stage 5: Validate - Ensure actionable insights
validate_result <- bid_validate(
  previous_stage = structure_result,
  summary_panel = "Executive summary highlighting key trends and required actions",
  collaboration = "Enable commenting and sharing of specific insights",
  next_steps = c(
    "Focus on the primary engagement metric trend",
    "Investigate any red-flag indicators",
    "Use drill-down for detailed analysis only when needed"
  )
)

After: Cognitively Optimized Dashboard

# The BID-informed approach: Progressive disclosure with clear hierarchy
ui_after <- page_fillable(
  theme = bs_theme(version = 5),

  # Primary insight first (addresses anchoring bias)
  layout_columns(
    col_widths = c(8, 4),

    # Key insight panel
    card(
      card_header(
        "📈 Engagement Health Score",
        class = "bg-primary text-white"
      ),
      layout_columns(
        value_box(
          title = "Overall Score",
          value = "87/100",
          showcase = bs_icon("speedometer2", size = "2em"),
          theme = "success",
          p(
            "↑ 5 points vs. last month",
            style = "font-size: 0.9em; color: #666;"
          )
        ),
        div(
          h5("Key Drivers", style = "margin-bottom: 10px;"),
          tags$ul(
            tags$li("DAU trending up (+12%)"),
            tags$li("Retention stable (73%)"),
            tags$li("⚠️ Session duration declining (-8%)")
          )
        )
      )
    ),

    # Action panel
    card(
      card_header("🎯 Focus Areas"),
      div(
        tags$div(
          class = "alert alert-warning",
          tags$strong("Attention needed:"),
          br(),
          "Session duration declining. Investigate user experience."
        ),
        actionButton(
          "investigate_sessions",
          "Investigate Session Trends",
          class = "btn btn-warning btn-sm"
        )
      )
    )
  ),

  # Secondary metrics (progressive disclosure)
  card(
    card_header(
      div(
        style = "display: flex; justify-content: space-between; align-items: center;",
        span("📊 Detailed Metrics"),
        actionButton(
          "toggle_details",
          "Show Details",
          class = "btn btn-outline-secondary btn-sm"
        )
      )
    ),

    # Hidden by default, shown on demand
    conditionalPanel(
      condition = "input.toggle_details % 2 == 1",
      layout_columns(
        col_widths = c(3, 3, 3, 3),
        value_box("DAU", "45.2K", icon = "people"),
        value_box("Retention", "73%", icon = "arrow-clockwise"),
        value_box("Sessions", "2.1M", icon = "activity"),
        value_box("Revenue", "$127K", icon = "currency-dollar")
      ),

      # Charts appear only when details are requested
      layout_columns(
        col_widths = c(6, 6),
        card(
          card_header("Engagement Trend"),
          plotOutput("engagement_trend_focused", height = "300px")
        ),
        card(
          card_header("Key Drivers Analysis"),
          plotOutput("drivers_analysis", height = "300px")
        )
      )
    )
  )
)

Key improvements:

Example 2: The “Data Dump” Report Problem

The Scenario

You’ve built a comprehensive sales dashboard that shows everything the sales team could possibly need. It’s technically perfect but nobody uses it.

Before: All Data, No Insights

ui_sales_before <- fluidPage(
  titlePanel("Q4 Sales Performance Dashboard"),

  # Massive filter section
  sidebarLayout(
    sidebarPanel(
      dateRangeInput("date_range", "Date Range"),
      selectInput("region", "Region", choices = regions, multiple = TRUE),
      selectInput(
        "product",
        "Product Line",
        choices = products,
        multiple = TRUE
      ),
      selectInput("salesperson", "Sales Rep", choices = reps, multiple = TRUE),
      selectInput(
        "customer_segment",
        "Customer Segment",
        choices = segments,
        multiple = TRUE
      ),
      selectInput(
        "deal_size",
        "Deal Size",
        choices = deal_sizes,
        multiple = TRUE
      ),
      checkboxGroupInput("deal_stage", "Deal Stages", choices = stages),
      numericInput("min_value", "Minimum Deal Value", value = 0),
      numericInput("max_value", "Maximum Deal Value", value = 1000000),
      radioButtons("currency", "Currency", choices = c("USD", "EUR", "GBP")),
      actionButton("apply_filters", "Apply Filters", class = "btn-primary")
    ),

    mainPanel(
      tabsetPanel(
        tabPanel(
          "Summary",
          fluidRow(
            column(3, valueBoxOutput("total_revenue")),
            column(3, valueBoxOutput("deal_count")),
            column(3, valueBoxOutput("avg_deal_size")),
            column(3, valueBoxOutput("win_rate"))
          ),
          plotOutput("revenue_chart", height = "600px")
        ),
        tabPanel("By Region", dataTableOutput("region_table")),
        tabPanel("By Product", dataTableOutput("product_table")),
        tabPanel("By Rep", dataTableOutput("rep_table")),
        tabPanel("Pipeline", dataTableOutput("pipeline_table")),
        tabPanel("Forecasting", plotOutput("forecast_chart"))
      )
    )
  )
)

The Problem: Analysis Paralysis

User feedback: “Takes forever to find what I need” and “I just export to Excel instead”

BID Solution: User-Centric Design

# Apply BID framework focusing on sales manager workflow
sales_interpret <- bid_interpret(
  central_question = "What deals need my attention this week?",
  data_story = list(
    hook = "Sales managers spend 2+ hours weekly creating status reports",
    context = "They need to quickly identify at-risk deals and top opportunities",
    tension = "Current data requires extensive filtering and analysis",
    resolution = "Provide intelligent prioritization with drill-down capability"
  ),
  user_personas = list(
    list(
      name = "Jennifer (Regional Sales Manager)",
      goals = "Identify at-risk deals, spot top opportunities, prepare for team meetings",
      pain_points = "Too much filtering required to find actionable insights",
      technical_level = "Intermediate"
    )
  )
)

sales_notice <- bid_notice(
  previous_stage = sales_interpret,
  problem = "Sales managers overwhelmed by filter complexity and data volume",
  evidence = "Users spend average 15 minutes per session just setting up filters, 40% abandon before getting insights"
)

sales_anticipate <- bid_anticipate(
  previous_stage = sales_notice,
  bias_mitigations = list(
    recency_bias = "Show deals by urgency, not just recent activity",
    confirmation_bias = "Highlight both positive and concerning trends",
    choice_overload = "Limit initial choices to most common use cases"
  )
)

sales_structure <- bid_structure(previous_stage = sales_anticipate)

After: Insight-Driven Sales Dashboard

ui_sales_after <- page_navbar(
  title = "Sales Command Center",
  theme = bs_theme(version = 5, preset = "bootstrap"),

  nav_panel(
    "🚨 Needs Attention",
    layout_columns(
      # Immediate action items
      card(
        card_header(
          "🔥 Urgent - Deals at Risk",
          class = "bg-danger text-white"
        ),
        card_body(
          p("3 deals worth $340K need immediate attention"),
          layout_columns(
            col_widths = c(6, 6),
            div(
              h6("MegaCorp Deal - $180K"),
              p(
                "❌ No activity in 14 days",
                style = "color: #dc3545; margin: 0;"
              ),
              p("Owner: Mike Chen", style = "font-size: 0.9em; color: #666;")
            ),
            div(
              actionButton(
                "view_megacorp",
                "View Details",
                class = "btn btn-sm btn-outline-danger"
              ),
              actionButton(
                "contact_mike",
                "Contact Mike",
                class = "btn btn-sm btn-danger"
              )
            )
          )
        )
      ),

      # Opportunities
      card(
        card_header("⭐ Hot Opportunities", class = "bg-success text-white"),
        card_body(
          p("2 deals worth $280K ready to close"),
          actionButton(
            "view_opportunities",
            "Review Opportunities",
            class = "btn btn-success btn-sm"
          )
        )
      )
    ),

    # Smart filters (only show when needed)
    conditionalPanel(
      condition = "input.show_filters",
      card(
        card_header("🔍 Refine Focus"),
        layout_columns(
          col_widths = c(3, 3, 3, 3),
          selectInput("quick_region", "Region", choices = c("All", regions)),
          selectInput(
            "quick_timeframe",
            "Timeframe",
            choices = c("This Week", "This Month", "This Quarter")
          ),
          selectInput(
            "quick_value",
            "Deal Size",
            choices = c("All", ">$50K", ">$100K", ">$250K")
          ),
          actionButton(
            "show_all_filters",
            "More Filters...",
            class = "btn btn-outline-secondary btn-sm"
          )
        )
      )
    )
  ),

  nav_panel(
    "📊 Performance",
    layout_columns(
      col_widths = c(4, 4, 4),
      value_box(
        "This Month",
        "$1.2M",
        "vs. $980K target (+22%)",
        showcase = bs_icon("graph-up"),
        theme = "success"
      ),
      value_box(
        "Pipeline Health",
        "Strong",
        "3.2x coverage ratio",
        showcase = bs_icon("speedometer2"),
        theme = "info"
      ),
      value_box(
        "Team Status",
        "On Track",
        "8 of 10 reps hitting quota",
        showcase = bs_icon("people"),
        theme = "success"
      )
    ),

    card(
      card_header("📈 Key Trends"),
      plotOutput("performance_trends", height = "400px")
    )
  ),

  nav_panel(
    "🎯 Team Focus",
    # Team-specific insights
    p("Individual rep performance and coaching opportunities...")
  )
)

Key improvements:

Example 3: The “Technical Metrics” Challenge

The Scenario

You’re monitoring application performance with detailed technical metrics. Your dashboard is perfect for engineers but executives get lost.

BID Solution: Audience-Aware Design

# Interpret stage: Understand different user needs
technical_interpret <- bid_interpret(
  central_question = "How is our application performing and what needs attention?",
  user_personas = list(
    list(
      name = "DevOps Engineer",
      goals = "Identify performance bottlenecks and system issues",
      pain_points = "Needs detailed metrics and historical trends",
      technical_level = "Expert"
    ),
    list(
      name = "Engineering Manager",
      goals = "Understand overall system health and team priorities",
      pain_points = "Needs summary view but ability to drill down",
      technical_level = "Advanced"
    ),
    list(
      name = "Executive",
      goals = "Understand business impact of technical issues",
      pain_points = "Technical details are overwhelming",
      technical_level = "Basic"
    )
  )
)

After: Multi-Audience Technical Dashboard

# Adaptive interface based on user role
ui_technical_after <- page_sidebar(
  sidebar = sidebar(
    # Role selector affects entire interface
    radioButtons(
      "user_role",
      "View Mode:",
      choices = c(
        "Executive Summary" = "executive",
        "Management View" = "manager",
        "Technical Details" = "engineer"
      ),
      selected = "executive"
    )
  ),

  # Executive view: Business impact focus
  conditionalPanel(
    condition = "input.user_role == 'executive'",
    h2("🟢 System Health: Good"),

    layout_columns(
      col_widths = c(6, 6),
      card(
        card_header("Business Impact"),
        value_box(
          "Service Availability",
          "99.8%",
          "Within SLA targets",
          theme = "success"
        ),
        value_box(
          "User Experience",
          "Good",
          "Page loads < 2 seconds",
          theme = "success"
        )
      ),
      card(
        card_header("Action Items"),
        div(
          class = "alert alert-info",
          "✅ No critical issues requiring immediate attention"
        ),
        p("Next scheduled maintenance: Friday 2am")
      )
    )
  ),

  # Manager view: Balance of summary and detail
  conditionalPanel(
    condition = "input.user_role == 'manager'",
    layout_columns(
      col_widths = c(3, 3, 3, 3),
      value_box("Uptime", "99.8%", theme = "success"),
      value_box("Response Time", "1.2s", theme = "success"),
      value_box("Error Rate", "0.02%", theme = "success"),
      value_box("Throughput", "15K/min", theme = "info")
    ),

    card(
      card_header("System Trends"),
      plotOutput("system_trends", height = "300px")
    ),

    card(
      card_header("Team Alerts"),
      p("2 minor alerts resolved this week"),
      actionButton("view_alerts", "View Alert History")
    )
  ),

  # Engineer view: Full technical detail
  conditionalPanel(
    condition = "input.user_role == 'engineer'",
    # Comprehensive technical metrics
    tabsetPanel(
      tabPanel("Performance", "Detailed performance metrics..."),
      tabPanel("Infrastructure", "Server and database metrics..."),
      tabPanel("Alerts", "Full alert history and configuration..."),
      tabPanel("Logs", "System logs and debugging info...")
    )
  )
)

Key insight: Same data, different presentations based on user cognitive needs and technical expertise.

Best Practices Summary

1. Start with User Intent, Not Data Structure

# ❌ Data-structure driven
ui_wrong <- tabPanel(
  "Database Tables",
  tabPanel("Users Table", dataTableOutput("users")),
  tabPanel("Orders Table", dataTableOutput("orders")),
  tabPanel("Products Table", dataTableOutput("products"))
)

# ✅ User-intent driven
ui_right <- tabPanel(
  "Customer Insights",
  card_body(
    h4("What customers need your attention?"),
    # Show actionable customer insights
  )
)

2. Progressive Disclosure Over Information Density

# ❌ Everything visible at once
ui_dense <- fluidRow(
  column(2, metric1),
  column(2, metric2),
  column(2, metric3),
  column(2, metric4),
  column(2, metric5),
  column(2, metric6)
)

# ✅ Key information first, details on demand
ui_progressive <- div(
  value_box("Key Metric", "Primary value"),
  actionButton("show_details", "View Supporting Metrics"),
  conditionalPanel(
    condition = "input.show_details",
    # Additional metrics here
  )
)

3. Context Over Raw Numbers

# ❌ Raw number without meaning
valueBox("Revenue", "$127,432")

# ✅ Number with context and meaning
value_box(
  "Revenue Progress",
  "$127K",
  "22% above $104K target",
  showcase = bs_icon("graph-up"),
  theme = "success"
)

Next Steps

  1. Pick one existing dashboard and apply the BID framework
  2. Start with Stage 1 (Interpret) - really understand your users’ goals
  3. Test with real users - measure time-to-insight and task completion
  4. Iterate systematically - use the same experimental rigor you apply to data analysis

Remember: The goal isn’t to become a UX expert, it’s to apply your analytical thinking to user experience and create more effective data products.

Use bid_concepts() to explore behavioral science principles, and check out the behavioral-science-primer vignette for deeper understanding of the psychological principles behind these improvements.