--- title: "Practical Examples: Before and After Dashboard Transformations" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Practical Examples: Dashboard Transformations} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` ```{r setup} 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: - **Before**: The technical-focused approach - **Problem**: What users actually experience - **BID Solution**: Systematic application of behavioral science - **After**: Improved user experience with code examples ## 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 ```{r everything-dashboard-before} # 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: ```{r bid-everything-solution} # 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 ```{r everything-dashboard-after} # 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:** - **Reduced cognitive load**: 1 primary score vs. 12 competing metrics - **Clear hierarchy**: Most important information first - **Progressive disclosure**: Details available but not overwhelming - **Actionable insights**: Clear focus areas and next steps ## 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 ```{r data-dump-before} 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 ```{r sales-bid-solution} # 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 ```{r sales-dashboard-after} 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:** - **Intelligent prioritization**: Shows what needs attention first - **Reduced choices**: Smart defaults instead of overwhelming filters - **Action-oriented**: Clear next steps, not just data - **Progressive complexity**: Simple view first, details on demand ## 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 ```{r technical-metrics-solution} # 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 ```{r technical-dashboard-after} # 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 ```{r user-intent-focus} # ❌ 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 ```{r progressive-disclosure} # ❌ 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 ```{r context-over-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.