
Practical Examples: Before and After Dashboard Transformations
practical-examples.Rmd
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
# 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:
- 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
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:
- 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
# 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
- Pick one existing dashboard and apply the BID framework
- Start with Stage 1 (Interpret) - really understand your users’ goals
- Test with real users - measure time-to-insight and task completion
- 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.