5  Business Dashboards with R

Flexdashboard is an R package that makes it easy to create interactive dashboards using R Markdown. These dashboards can integrate various components (plots, tables, value boxes, gauges, etc.) and can be either static (pure HTML/JS interactivity) or dynamic (enhanced with Shiny for user-driven interaction). The goal is to present data visualizations in a coherent, shareable format – useful for business reports, academic presentations, or government analytics. In fact, many organizations use R-based dashboards as part of their digital transformation (Government 4.0 / Industry 4.0). For example, New Zealand’s government agencies have built interactive dashboards like the Trade Intelligence Dashboard, which provides up-to-date data on exports and imports with intuitive graphs and tables, as well as COVID-19 surveillance dashboards and tourism visitor dashboards to inform policy and the public. These real-world examples show how effective a well-designed R dashboard can be in conveying insights to decision-makers and stakeholders.

Use Case: Imagine you are a data analyst at QMIB Consulting Inc., tasked with advising an international investment firm on which country to invest in. Rather than static slides, you can create a dynamic presentation using a flexdashboard. By incorporating key economic indicators (for instance, GDP growth and Foreign Direct Investment inflows since 1995 for a country like France), the board of directors can interact with the data – viewing trends, comparing metrics, and drilling down into details. In this chapter, we learn how to build such dashboards step by step, covering layout options, interactive components, and useful techniques to refine the dashboard’s appearance and usability.

5.1 Getting Started with Flexdashboard

To begin building dashboards in R, you’ll first need to install and load the flexdashboard package. This can be done from CRAN via:

install.packages("flexdashboard")
library(flexdashboard)

Flexdashboard works as a format for R Markdown, so you create a new R Markdown document and specify the flexdashboard format. RStudio makes this easy: you can go to File > New File > R Markdown…, then choose From Template, and select Flex Dashboard. This automatically generates a sample .Rmd file with the YAML header configured for flexdashboard. The YAML front-matter will look something like:

---
title: "My Dashboard"
output: flexdashboard::flex_dashboard
---

You can add other options here (we’ll see examples for layouts, theming, etc.). Once your Rmd is set up, you organize content into sections using markdown headings and code chunks – and upon knitting the document, you get an interactive dashboard in HTML.

Basic Structure: In a flexdashboard, the layout is determined by the heading levels. By default, top-level headings (#) can define pages (if you have multiple dashboards in one, as tabs), second-level headings (##) define columns (by default), and third-level headings (###) define individual components (like charts, tables, etc.) within those columns. However, you can also configure the orientation to be by rows instead, as explained below. Each component can be an R code chunk that generates a plot or output, or even just markdown text for explanations.

5.2 Layouts and Orientation

One of the first decisions in designing a dashboard is how to arrange the components on the screen. Flexdashboard offers flexible layout options – you can arrange components in columns or rows, have them fill the page height or scroll, and even combine layouts using tabbed sections. Here are the key layout principles:

  • Columns vs. Rows: By default, dashboards are organized into columns. Each second-level heading (##) starts a new column, and components (### headings) under it will stack vertically within that column. Alternatively, you can orient the layout by rows by setting orientation: rows in the YAML. In a row-oriented layout, each second-level heading defines a full-width row, and third-level headings within a row will distribute into columns within that row. Choosing rows vs. columns depends on which direction you want components to primarily flow. You might use rows to create a “featured section” spanning full width (as in a focal chart layout), or stick with columns to keep a side-by-side panel structure.

  • Filling page height vs. Scrolling: By default, flexdashboard tries to fill the browser height with your content (vertical stretching), which is convenient when you have just a few charts. If you have many components stacked, though, it may squeeze them too small. You can toggle this behavior with the vertical_layout option in YAML. Using vertical_layout: fill will resize charts to fill the page height, whereas vertical_layout: scroll will keep each chart at its natural height and add a scrollbar for overflow. In other words, fill makes the dashboard a full single-page app without scrolling (charts flex to use available space), and scroll allows the page to scroll normally (charts keep their set height).

Chart Stack Layouts (Fill vs. Scroll)

If you simply stack several charts one above the other in a single column, you have a chart stack layout. The difference between using fill or scroll is evident in how the charts size themselves on screen. With vertical_layout: fill, the charts expand or contract vertically so that they collectively occupy the full viewport height (useful if you want to avoid scrolling). With vertical_layout: scroll, each chart keeps a fixed height, so if the column is taller than the screen, the user can scroll down to see all charts.

Example – A scrolling layout with three charts stacked vertically. In the image above, the dashboard has three charts (Chart 1, Chart 2, Chart 3) in one column, and because vertical_layout: scroll is used, each chart remains at its default height and the page provides a scrollbar to accommodate the overflow. In a fill layout (not pictured), those three charts would instead stretch/shrink to fill the screen without scrolling, which can be ideal for a small number of items but less so for many components. Choosing between fill and scroll often comes down to how much content you have vertically – use fill for concise dashboards and scroll for longer dashboards where preserving each plot’s aspect ratio is important.

Tabbed Sections (Tabsets)

Dashboards can get crowded if you try to show everything at once. Tabsets allow you to place multiple components in the same space, with tabs for the user to switch between them. This is ideal when you have related charts or info that can be viewed one at a time. In flexdashboard, creating a tabset is as simple as adding a .tabset class to a heading. You can make an entire column a tabset or a row a tabset by appending {.tabset} after the heading line.

For example, say you want two charts to occupy the same area as tabs (instead of one above the other). You could do:

Column {.tabset}
-------------------------------------
### Chart 2  
*(content for chart 2)*

### Chart 3  
*(content for chart 3)*

Now Charts 2 and 3 will render as two tabs in that column, with Chart 2 shown by default and a tab for Chart 3. You can also add an optional .tabset-fade class for a nice fade-in transition when switching tabs. Tabsets can be used within a row or column – e.g., you might have a row with one component always visible and another row where multiple components are in tabs.

Using a tabset to conserve space: In the image above, the second column was defined with {.tabset}, so Chart 2 and Chart 3 are in a tabbed container. The user can click the tabs to toggle between those charts. This approach keeps the dashboard cleaner by not showing all components at once. In many cases, tabsets are preferable to long scrolling pages for optional or detailed content, because they let readers easily navigate between views without losing context. You can even lay out an entire row as a tabset (tabs spanning the full width) if needed – just add .tabset to the row heading, as shown in the example for a “Tabset Row” layout.

5.3 Key Components: Value Boxes and Gauges

Beyond plots and tables, flexdashboard provides special components to highlight important metrics:

  • Value Boxes: A value box is a UI element that displays a single numeric value with a caption and an icon, in an attractively colored box. It’s great for showing key performance indicators (KPIs) like “Total Sales: 1.2M” or “Conversions: 5%”. In flexdashboard, you create one with the valueBox(value, caption, icon = "icon-name") function, typically inside an R code chunk. By default, value boxes are placed in a row and will automatically be styled to fit evenly. For example:

    valueBox(45, "Articles per Day", icon = "fa-pencil")

    would show a box with 45 and a pencil icon, labeled Articles per Day. You can specify icons from Font Awesome, Ionicons, or Bootstrap’s Glyphicon set – just use the appropriate prefix ("fa-...", "ion-...", or "glyphicon-..."). There’s also a color parameter to change the box color. Flexdashboard comes with some preset color names (“primary”, “info”, “success”, “warning”, “danger”), which correspond to Bootstrap theme colors, or you can use any custom CSS color code. In the example below, the third value box uses a conditional color: it shows blue (“primary”) if the value is low, but would turn amber (“warning”) if above 10.

Value boxes highlighting KPIs: This dashboard row shows three value boxes side by side – 45 with a pencil icon, 126 with a comments icon, and 5 with a trash can icon. Each box includes a short label (e.g., “Articles per Day”) to clarify the metric. The third box’s color is set dynamically based on its value (in this case 5 is a low number, so the box is in the default color). Value boxes make it easy to call out key numbers at a glance. If the value has more details on another page of the dashboard, you can even make the box clickable by adding an href="#section-id" parameter to valueBox() – clicking it will navigate to that section for deeper insight.

Note: Not every icon from Font Awesome or Ionicons is free or available by default, so occasionally an icon might not render if it requires a paid/pro license or if the name is wrong. Always double-check icon names, and if an icon doesn’t show up, try a different one or ensure the icon set is supported.

  • Gauges: A gauge displays a numeric value on a dial (meter) with colored zones to indicate ranges (e.g., poor/fair/good, or low/medium/high). They are useful for percentages or scores relative to a target. In flexdashboard, the gauge() function creates a gauge. For example:

    gauge(90, min = 0, max = 100, symbol = '%', 
          gaugeSectors(success = c(80,100), warning = c(40,79), danger = c(0,39)))

    This would draw a gauge for 90% with a range from 0 to 100, and define custom color sectors: values 80–100 as “success” (typically green), 40–79 as “warning” (orange), and 0–39 as “danger” (red). The symbol='%' simply appends a % sign to the displayed value. If you omit custom sectors, the gauge will just use a default single-color arc. Like valueBox, you can hyperlink a gauge via href if it should lead to more details elsewhere.

Gauges showing metric levels: Here we see three gauges side by side – for example, Contact Rate: 90% in green (within the success range), Average Rating: 37.4 in orange (warning range), and Cancellations: 7 in red (danger zone). The sectors were defined such that thresholds trigger these colors. Gauges give an immediate visual sense of where a value lies within a range, much like a speedometer. They can be more eye-catching than plain numbers, but use them sparingly for metrics where the context of min/max is important. As with value boxes, if you’re building an interactive Shiny dashboard, you would wrap gauge outputs in renderGauge() (and value boxes in renderValueBox()) to allow them to update reactively – but for a static flexdashboard (pure Rmd), you just call gauge() directly with static or precomputed values.

5.4 Advanced Layout Refinements

Once you have basic layouts and components in place, you may want to fine-tune the appearance:

  • Precise Chart Sizing: We discussed using data-height and data-width for rows and columns. You can also apply a {data-height=...} attribute directly to a component’s heading to give that particular chart a fixed height (relative to siblings). For instance:

    ### Sales Over Time {data-height=300}

    ensures that plot gets 300 pixels (or proportionate units) of height. This is useful if one plot should be taller/shorter than others in the same column. Keep in mind that if vertical_layout: scroll is active, the page will scroll normally once the fixed heights exceed the screen. If vertical_layout: fill is active, the heights you set will be used as relative weights, not absolute pixel counts.

  • Example – Emphasizing One Chart: Suppose we want the first chart in a column to be larger. We could do:

    ### Chart 1 {data-height=2}  
    ### Chart 2 {data-height=1}  
    ### Chart 3 {data-height=1}  

    In a fill layout, Chart 1 will get 50% of the column’s height while Charts 2 and 3 each get 25%, since we gave Chart 1 a weight of 2 and the others 1 each (2:1:1 ratio). In a scrolling layout, those data-heights would simply translate to those pixel heights if possible. An alternative is to use separate rows as we did in focal layouts – either method can achieve a similar effect of a bigger first chart. Figure out which approach works better based on whether you want scrolling or not, and how independent the sections are.

  • Multiple Pages: If you have a lot of content or logically separate sections (e.g., different topics or audiences), you can split a flexdashboard into multiple pages. Each page gets its own top-level navigation tab in the final output. To create pages, use first-level headings (# Page Title) in the R Markdown. For example:

    # Overview  
    ... (dashboard content) ...  
    
    # Details  
    ... (more dashboard content) ...

    Each # heading creates a new page tab. This is a convenient way to organize dashboards that would otherwise be very long. For instance, you might have an Overview page with high-level KPIs and summary charts, and separate pages for Regional Details, Historical Trends, etc. Flexdashboard will automatically include a navigation bar at the top with the page titles for easy switching. If you have more than about 5 pages, consider grouping them into a dropdown menu using the data-navmenu attribute (this allows nesting pages under a menu heading in the nav bar). You can also hide a page from the nav bar by adding {.hidden} if you want to link to it internally without cluttering the menu.

  • Navigation Links: Within your dashboard text, you can create links to other pages using the page title or an anchor. For example, [Details](#details) would link to the page titled “Details” (whose heading might be # Details in the source). This can help guide users, like “See more on the Details tab.”

5.5 Storyboards for Data Narratives

Flexdashboard includes an innovative layout called storyboard, which is perfect for presenting a sequence of visualizations along with commentary – like telling a story with your data. In a storyboard, each “frame” is shown one at a time (like slides), with navigation arrows or thumbnails to move through them.

To use a storyboard layout, you can either make an entire dashboard a storyboard or just one page of it a storyboard:

  • Single-Page Storyboard: In YAML, set storyboard: true under the flex_dashboard output. Then each level 3 heading (###) you write becomes a frame in the storyboard. The title of that section is used as the caption or label for that frame’s navigation. So you might do:

    output: 
      flexdashboard::flex_dashboard:
        storyboard: true

    in the header, and then in the body:

    ### Frame 1 – Introduction  
    *(some chart or content)*  
    
    ### Frame 2 – Analysis  
    *(some chart or content)*  
    
    ### Frame 3 – Conclusion  
    *(some chart or content)*

    When rendered, the dashboard will show only one frame at a time with controls (usually dots or arrows) to switch frames, much like a slideshow. This is great for focusing the audience’s attention step by step.

Storyboard layout example: The image above shows a storyboard in action (from an HTML widgets showcase). Each frame presents a visualization (e.g., a map or chart) with its own caption, and the user can navigate through frames sequentially. To create this, you simply designate the storyboard in YAML and use multiple ### sections, as described. Storyboards allow you to maintain an ordered narrative in your dashboard, which can be very engaging for viewers who prefer a guided analysis rather than exploring everything at once.

  • Storyboard as Part of Multi-Page Dashboard: You aren’t limited to making the whole dashboard a storyboard. You can have normal pages and one page that is a storyboard. Instead of storyboard: true globally, you keep the default and just add the class .storyboard to a page heading. For example:

    # Analysis {.storyboard}  
    ## *(frames here as ### sections)*  
    
    # Details  
    ## *(normal layout sections)*

    This way, the Analysis tab in the navbar would be a storyboard, and the Details tab could be a regular dashboard page with free-form layout. This hybrid approach is useful if you want a presentation-style walkthrough for some part of your report, but also include other pages for free exploration.

  • Commentary Sidebars: A neat feature in storyboards is the ability to add a commentary or explanation panel to the right side of a frame. You do this by inserting a horizontal rule (***) within a frame’s content. Text after the *** will appear as a sidebar next to the main frame content. This is great for adding descriptive narration or bullet points alongside a chart. You can even adjust the width of the commentary panel per frame with an attribute like {data-commentary-width=400} on the frame’s heading (default is 300px wide). For example:

    ### Frame 2 – Revenue Over Time {data-commentary-width=400}  
    *(plot code)*  
    
    ***  
    
    **Note:** The spike in 2020 corresponds to a one-time event...

    This would show the plot on the left and a 400px sidebar on the right with the note. Using commentary in storyboards helps keep the visual uncluttered while still providing context and analysis to the viewer.

5.6 Theming and Customizing Appearance

By default, flexdashboards use a standard theme that inherits from Bootstrap (the popular CSS framework). You can customize the look in various ways:

  • Built-in Themes: The flexdashboard format supports a theme: option in YAML to easily apply Bootstrap themes (via the bslib package). For instance, you might set:

    output:
      flexdashboard::flex_dashboard:
        theme: cosmo

    to use the “Cosmo” Bootswatch theme, or use bootswatch: minty under a theme: list for more control. Theming can change colors, fonts, and overall style without manually writing CSS. The RStudio flexdashboard documentation provides details on using Bootswatch themes and even custom themes with bslib if you need fine-grained control. Choose a theme that matches your audience or corporate style guidelines – for example, a dark theme for a sleek modern look, or a light theme with brand colors.

  • Custom CSS: If the built-in theming isn’t enough, you can add your own CSS file via the YAML (using the css: my-styles.css option) and override styles. This requires knowledge of CSS, but it lets you do things like change font sizes, colors of specific elements, spacing, etc. For instance, you could define a CSS class to color certain backgrounds and then apply that class to specific elements in your dashboard.

  • Responsive Design (Mobile): Flexdashboards are mobile-friendly by design. The layout adapts automatically to smaller screens. On a tablet, the view is similar to desktop but scaled; on a phone (narrow width ≤ 768px), the dashboard will collapse into a single column for easier vertical scrolling. This means if you had multiple columns side by side, on a phone they will stack one on top of another. Also, certain widgets behave differently on mobile: tables won’t use horizontal scroll (they’ll show fewer columns or use pagination) to avoid messing with the overall scroll, and plots might be resized. You generally don’t need to do much to support mobile, but it’s good to test your dashboard on a phone. If some components really don’t make sense on a small screen, you can add the .no-mobile class to hide them on phones, or even create a specially simplified version of a section with a .mobile class that will only show up on mobile (replacing the standard version). The ability to target mobile specifically ensures your key message is not lost on smaller devices.

  • Page Title, Navbar, and Logos: The dashboard’s title (from YAML) is displayed in the navbar. You can also add an author name and date if desired in YAML for display. The navbar by default includes the page tabs (for multi-page dashboards). You can customize the navbar by adding icons or links (e.g., to your website or a help page) using the navbar YAML option. For example, you could include a link to the source code or a home icon. Social sharing links (Twitter, Facebook, etc.) can be added via social: ["twitter", "facebook", ...] if you want viewers to share your dashboard. While these are small touches, they can make your dashboard look more polished and provide users with easy ways to navigate or get more information.

5.7 Building an Example Dashboard – Investing in France

Finally, let’s apply these concepts in the scenario mentioned at the start. The task is to create a dynamic dashboard for an investment firm, showing data that will help decide whether to invest in a particular country. We’ll use France as an example, and include two indicators over time: GDP and Foreign Direct Investment (FDI), from 1995 onward. The data for these indicators can be obtained from sources like the World Bank (for example via the WDI package in R) or from the provided dataset on GitHub (if a prepared dataset is available). The idea is to demonstrate how flexdashboard can be used to present economic trends interactively.

Data Preparation: We need annual (or quarterly) data for France’s GDP and FDI from 1995 to recent year. GDP could be measured in USD (or GDP growth %), and FDI typically as net inflows (% of GDP or total USD). For simplicity, assume we have a CSV or we fetch via an API. In R, we might do something like:

# Using WDI package to fetch data (if internet available)
library(WDI)
data <- WDI(country="FR", indicator=c(GDP="NY.GDP.MKTP.CD", FDI="BX.KLT.DINV.CD.WD"), start=1995, end=2022)

This would give us a data frame of GDP (NY.GDP.MKTP.CD = GDP in current USD) and FDI (net inflows of FDI in USD) for France by year. Alternatively, we use the provided data file from GitHub which presumably already contains the needed series.

Dashboard Design: For the presentation, a good layout might be: a title page (or intro section), then a page with the main charts. We can use a single-page dashboard if it’s not too busy, or multiple pages if we want to separate aspects (e.g., one page for GDP, one for FDI, and one for comparison or summary).

A possible approach is: one column for GDP trend, another column for FDI trend, and a value box or two for summary statistics (like latest GDP value or growth rate, and latest FDI amount). We could also incorporate a tabset if we want to show multiple countries for comparison – but since the task is one country, we might instead use a storyboard or some narration.

For instance, Page 1: Overview – show France’s GDP and FDI over time in two side-by-side plots, with a short analysis. Page 2: Details – maybe break down the FDI into sectors or show France in context of other countries (if data available), etc. Given the assignment, keeping it simple is fine: a single page with both indicators.

We will definitely make the plots interactive (using plotly::ggplotly or dygraphs) so the board members can hover over points to see exact values. Flexdashboard supports any htmlwidget out of the box, meaning if we create an interactive plot in R (with Plotly, Leaflet, Highcharter, etc.), it will seamlessly work in the dashboard.

Implementation Sketch:

  • YAML: Use title: "Investment in France – GDP & FDI Dashboard" and output: flexdashboard::flex_dashboard. Possibly specify orientation: columns (default) because we’ll put charts side by side. If we want the charts to fill screen height, we could use vertical_layout: fill since we only have two charts vertically (one per column), so they can stretch nicely. If using fill, ensure their relative heights match (or just one per column, so fill is straightforward).

  • Content:

    • Column 1: GDP Trend – a line chart of France’s GDP over years (inflation-adjusted or not). We can title it “GDP (Current USD)” or similar and perhaps add a small note. This could be a ggplot or base R plot that we turn interactive. Under the hood, one might do: renderPlot({ ... }) if using Shiny, but in static we do directly output the plot. For interactivity without Shiny, using plotly::ggplotly() on a ggplot object is simplest. That yields a plotly chart where you can hover and zoom.
    • Column 2: FDI Trend – similarly, a line or bar chart for FDI net inflows each year. Possibly FDI is more volatile, so maybe bars per year could show inflows. Or line if continuous. We label it “FDI Net Inflows”. Again, make it interactive via plotly or dygraphs.
    • Value Boxes (optional): We could add a top row (Row level heading) above these columns with some high-level values. For example, a valueBox for “Latest GDP” and one for “Latest FDI” and perhaps one for “Average Growth Rate”. These could sit in a single row above the two-column layout. Value boxes will automatically size equally in a row. For navigation, if we had a details page, we could link them via href (e.g., clicking the FDI value box jumps to an FDI detail page).
    • Comments: Add narrative text where needed – perhaps below the charts or in a sidebar if using a different layout – to explain what the trends mean (e.g., “FDI peaked around 2007 before the financial crisis, then dipped, etc.”).

Given that this is just an outline, the actual coding would involve reading the data and plotting. But once the structure is in place, knitting the Rmd will produce the interactive dashboard HTML which you can open in a browser or host on an RStudio Connect/shinyapps.io for others to view.

Expected Outcome: The board of directors would see an interactive report where they can switch between GDP and FDI (if on separate tabs or pages, or side by side to compare), hover to get exact figures for each year, and perhaps use filters (if we included any Shiny inputs for interactivity, like selecting different countries – though that’s an extension). The use of flexdashboard ensures the presentation is self-contained and reproducible (data + code + narrative together), and it can be easily updated next quarter or replicated for another country by just changing a parameter.

Finally, to get a better idea, you can refer to an example dashboard provided (see the link in the chapter materials). It demonstrates a possible solution where France’s GDP and FDI data are presented with interactivity. That example shows one way to structure the flexdashboard for this task, including how the plots and value boxes are arranged.

5.8 Conclusion

Flexdashboard is a powerful tool for creating interactive dashboards entirely within R Markdown, which means you can mix R code, results, and commentary in one document. Summarizing Chapter 11’s lessons:

  • We can use R Markdown to publish groups of related visualizations as a dashboard, rather than static reports.
  • Flexdashboard supports a wide variety of components – from interactive htmlwidgets (Plotly, Leaflet, etc.) to base R plots, tables (with the DT package), as well as specialized elements like value boxes and gauges. This makes it suitable for many types of data displays.
  • There are multiple layout options: you can arrange content in rows or columns, use tabsets, include multiple pages, or even create storyboards for a step-by-step narrative. This flexibility lets you design the dashboard that best fits your story and your data.
  • While flexdashboards can be static (just HTML/JS), you have the option to integrate Shiny for more advanced interactivity – for example, adding dropdown selectors, filters, or reactive inputs that let viewers customize what they see. This bridges the gap between a document and a full web app, all with relatively simple additions to your R Markdown.

In essence, flexdashboard allows even those with modest web development experience to create professional, interactive dashboards using R, enabling better communication of data insights. With the techniques covered – layout customization, interactive components, theming, and more – you can craft dashboards that are informative, visually appealing, and tailored to your audience’s needs. Now it’s time to apply these skills and start building your own dashboard, be it for business intelligence, academic research, or public data storytelling!

Sources:

  • RStudio (Posit) Flexdashboard Documentation – Using flexdashboard (examples and explanations of layout, components, etc.)
  • RStudio Flexdashboard Documentation – Layouts and Storyboards (multiple pages, storyboard usage, mobile design adaptations)
  • Data Pipeline with R – Chapter 11 Dashboards (educational material summarizing flexdashboard capabilities and an illustrative use case)
  • New Zealand MBIE – Trade Intelligence Dashboard (example of a real-world R Shiny dashboard for trade data)