ggplot()
: GO0
ggplot()
: GO0
Objectives
In this chapter I will learn to use the {bslib} package to create a Shiny dashboard for my RWB project.
I am thinking on a dashboard with the following functionalities:
Year
: Allow only one year for map.Score
: One of the available scores:
Ranking
: Not allowed (or later perhaps a map of the ranking differences between two years?)Region
: is allowed with map mode, but takes the chosen map and zooms into the selected region. Similar as in WHR website by choosing “Map” and the select by “Filter by region”.Country
: Select one or more of the available countries (because filtered by region was choose, then only the countries of this region are eligible.Year
: Default is all available years, but the user can alternatively (de)select years.Score
: One of the available scores:
Ranking
: Alternatively with Score
All year is default, but (de)selecting years is possible.Region
: One region is eligible. Default is all countries.Country
: One or more countries are eligible. If a region is filtered then one can add other countries or deselect some countries from the region.Score chart
: Global and component scores over all available yearsRanking chart
: Global and component rankings over all available yearsValue boxes
: Current, highest, lowest, average, medium, biggest climb, biggest fall (only rankings and global score)I am condensing the most important section for my project of the {bslib} Dashboard article.
There only three layout elements available in the standard layout:
R Code 6.1 : UI elements of the most simple dashboard
shinylive-r
output?
In the above first example there is only a minimal interaction possible. Clicking on the right top arrow in the sidebar (<) will close the sidebar and therefore enlarge the main content area. But in this {shinylive} mode you can also change the code and rerun the program by clicking on the top right filled arrow (▶). Try it out to change one of the text strings.
Both the
sidebar
and main content areas can hold any arbitrary collection of UI elements, but it’s good practice to keep inputs in thesidebar
and outputs in the main content area. Also, consider wrapping outputs in acard()
and sidebar contents in asidebar()
object to add atitle
and/or customize styling, positioning, etc.
Even with the minimum card content we needed about 15 code lines. This is just a simple template. All parts can be arbitrary complex - title()
could be for example a complex expression - sidebar()
can hold many input controls and directives for the layout (e.g., several cards in a specific arrangement), - card()
can hold complex results and server()
is the most tricky part, because it needs reactivity (reactive()
functions) to work together with the input controls.
I have developed a template as an RStudio snippet with the minimum content example. I am using it to start new Shiny apps or a shinylive-r
code chunks.
I will stop here with the minimum content example. It works as a template for new apps resp. shinylive-r
code chunks. But there are still to cover many other {bslib} design features and functions. Whenever the need arises I will either come back here to this section or write my notes in the appropriate project section.
In the {bslib} documentation there is a basic example of a customizable histogram with numeric variables from the {palmerpenguins} dataset. I will use my own rwb
dataset to display different line charts for global scores and rankings for selected countries or regions.
My aim is to go step by step from the simple to the more complex, e.g. to start with a line chart for one country and one variable, followed for several variables and finally with several countries. For my own learning purpose I will also use four different modes:
Program | Helper | Shiny | Abbr. |
---|---|---|---|
ggplot2 | only | —– | GO0 |
plotly | only | —– | PO0 |
ggplot2 | with | Shiny | GWS |
plotly | with | Shiny | PWS |
The final product should always be an interactive graph using {plotly} in an web application environment controlled by {shiny} PWS.
In the first try I will not give attention to legend and theme but one: theme_set(theme_bw())
To facilitate learning I will apply in this book three conventions:
shinylive-r
code chunks in as separate Shiny app because then I have all available the debug tools. Then I will include the content of the finished app into the shinylive-r
code chunk with## file: app.R
{{< include path-to/app.R >}}
The easiest line chart is a graph showing the development of one variable over the years for one country. I will take the variable score
for the global score showing the trend for my own country Austria (country_en == "Austria
).
The UI is identical with R Code 6.6. There are two ways to create a {plotly} line chart:
ggplot()
to plotly()
. This requires only one changes: Encapsulate the ggplot2::ggplot()
object with plotly::ggplotly()
.plot_ly()
graph from scratch. For me this requires to learn another syntax because my experience with {plotly} is currently very limited. But using this direct approach has some advantages:ggplotly()
compared to the native plot_ly()
. The difference is huge: Depending on the graph ggplotly()
is 23-143 (!) times slower than plot_ly()
.ggplotly()
is not always predictable and there is less control about the final graph compared with the native plot_ly()
function.R Code 6.4 : Line chart with ggplot()
and ggplotly()
.
ggplot()
and ggplotly()
Development of the global World Press Freedom Index (WPFI) of Austria with ggplot()
and ggplotly()
: GO0
R Code 6.5 : PO0
Using {plotly} (without Shiny) for a line chart
GO0
library(plotly, warn.conflicts = FALSE)
library(dplyr, warn.conflicts = FALSE)
rwb <- readRDS(paste0(here::here(), "/data/chap011/rwb/rwb.rds"))
p <- rwb |>
select(year_n, country_en, score) |>
filter(country_en == "Austria") |>
na.omit() |>
plot_ly(
x = ~year_n,
y = ~score,
type = 'scatter',
mode = 'lines')
p
Development of the global World Press Freedom Index (WPFI) of Austria with {plotly}: PO0
During the process of loading and attaching the two packages ({dplyr} and {plotly}) I have used warn.conflicts = FALSE
to suppress warnings. This is special for these two packages. A general command would have been base::suppressWarnings()
.
Now I have to think about the input control(s) for the user. In this first simple example I will only provide to choose one country, specifically only one country. The variable is with score
still the same.
I had problems to work with my RWB dataset. For shinylive-r
code chunks is a special procedure necessary to load external data files: There are three ways to include files:
rwb
is a binary file.How to include .rds file in shinylive (from Brave KI)
To include an .rds
file in a Shiny app hosted on the internet, you can load it directly from a public URL using the readRDS
function combined with gzcon
and url
to handle the remote data stream. This method allows you to access the file without needing to download it locally first.
For example, if the .rds
file is hosted on a public repository like GitHub, you can use the raw file URL:
This approach works because url()
creates a connection to the remote file, gzcon()
decompresses it if necessary, and readRDS()
reads the R object from the connection This method is particularly useful for deploying Shiny apps on platforms like shinyapps.io, where you can host the data file publicly and reference it directly in your app’s server logic
Alternatively, you can create a helper function to manage the remote loading process, which saves the file to a temporary local location before reading it, ensuring compatibility and reliability:
readRDS_remote <- function(file, quiet = TRUE) {
if (grepl("^http", file, ignore.case = TRUE)) {
file_local <- file.path(tempdir(), basename(file))
download.file(file, file_local, quiet = quiet, mode = "wb")
file <- file_local
}
readRDS(file)
}
Then use it as:
data <- readRDS_remote("https://example.com/data/file.rds")
This method is beneficial when dealing with large files or unreliable connections, as it ensures the file is fully downloaded before being read.
In addition to the line chart I have added two customizations:
shape
, size
, color
and more.paste()
function to put the static and dynamic parts of text string together in a reactive function (and not in UI).ggploty()
Converting a ggplot()
to plotly()
in a Shiny app requires three changes:
ggplot2::ggplot()
object with plotly::ggplotly()
. This is the same change as in Listing / Output 6.2 without Shiny. But there are other two additions necessary:plotOutput()
to plotlyOutput()
and torenderPlot()
to renderPlotly()
.R Code 6.7 : Choose country to display WPFI for all available years with ggplotly()
plot_ly()
The second possibility is to use the native mode to {plotly}: Instead of converting a {ggplot2} graph to {plotly} we generate the interactive graph with plotly::plot_ly()
.
As this is the more convenient approach to build interactive graphs for complex figures and dashboards computing several charts in parallel, from now on I will only display the native plot_ly()
variant.
R Code 6.8 : Choose country to display WPFI for all available years with ggplotly()
plot_ly()
syntax
x
and y
variable need in front the ~
sign. These are the data visualized as scatter point or lines in the x
and y
variable.scatter
is a fundamental type for creating various visualizations such as scatter plots, line charts, but is also used for text and bubble charts.mode
attribute determines how the data is displayed, such as with markers, lines, text, or a combination of these. For example, setting mode = "line"
creates a standard line plot as in Listing / Output 6.3, while mode = "lines+markers"
adds both lines connecting the points and markers at each point as in R Code 6.8.Resource 6.1 : How to build line and scatter plots with {plotly}
scatter
type has a rich set of customization options.term | definition |
---|---|
RWB | Reporters Without Borders (RWB), known by its French name Reporters sans frontières and acronym RSF, is an international non-profit and non-governmental organization headquartered in Paris, France, founded in 1985 in Montpellier by journalists Robert Ménard, Rémy Loury, Jacques Molénat, and Émilien Jubineau. It is dedicated to safeguarding the right to freedom of information and defends journalists and media personnel who are imprisoned, persecuted, or at risk for their work. The organization has consultative status at the United Nations, UNESCO, the Council of Europe, and the International Organisation of the Francophonie. |
Snippet | An RStudio snippet is a text macro or code template used to quickly insert commonly used pieces of code, automating the process of typing repetitive or boilerplate code. See: https://rstudio.github.io/rstudio-extensions/rstudio_snippets.html |
WHR | The World Happiness Reports are a partnership of Gallup, the Oxford Wellbeing Research Centre, the UN Sustainable Development Solutions Network, and the WHR’s Editorial Board. The report is produced under the editorial control of the WHR Editorial Board. The Reports reflects a worldwide demand for more attention to happiness and well-being as criteria for government policy. It reviews the state of happiness in the world today and shows how the science of happiness explains personal and national variations in happiness. (https://worldhappiness.report/about/) |
Session Info
xfun::session_info()
#> R version 4.5.1 (2025-06-13)
#> Platform: aarch64-apple-darwin20
#> Running under: macOS Sequoia 15.6.1
#>
#> Locale: en_US.UTF-8 / en_US.UTF-8 / en_US.UTF-8 / C / en_US.UTF-8 / en_US.UTF-8
#>
#> Package version:
#> askpass_1.2.1 base64enc_0.1.3 bslib_0.9.0 cachem_1.1.0
#> cli_3.6.5 commonmark_2.0.0 compiler_4.5.1 cpp11_0.5.2
#> crosstalk_1.2.2 curl_7.0.0 data.table_1.17.8 digest_0.6.37
#> dplyr_1.1.4 evaluate_1.0.5 farver_2.1.2 fastmap_1.2.0
#> fontawesome_0.5.3 fs_1.6.6 generics_0.1.4 ggplot2_3.5.2
#> glossary_1.0.0 glue_1.8.0 graphics_4.5.1 grDevices_4.5.1
#> grid_4.5.1 gtable_0.3.6 here_1.0.1 highr_0.11
#> htmltools_0.5.8.1 htmlwidgets_1.6.4 httr_1.4.7 isoband_0.2.7
#> jquerylib_0.1.4 jsonlite_2.0.0 kableExtra_1.4.0 knitr_1.50
#> labeling_0.4.3 later_1.4.4 lattice_0.22.7 lazyeval_0.2.2
#> lifecycle_1.0.4 litedown_0.7 magrittr_2.0.3 markdown_2.0
#> MASS_7.3.65 Matrix_1.7.4 memoise_2.0.1 methods_4.5.1
#> mgcv_1.9.3 mime_0.13 nlme_3.1.168 openssl_2.3.3
#> pillar_1.11.0 pkgconfig_2.0.3 plotly_4.11.0 promises_1.3.3
#> purrr_1.1.0 R6_2.6.1 rappdirs_0.3.3 RColorBrewer_1.1-3
#> Rcpp_1.1.0 renv_1.1.5 rlang_1.1.6 rmarkdown_2.29
#> rprojroot_2.1.1 rstudioapi_0.17.1 rversions_2.1.2 rvest_1.0.5
#> sass_0.4.10 scales_1.4.0 selectr_0.4.2 splines_4.5.1
#> stats_4.5.1 stringi_1.8.7 stringr_1.5.1 svglite_2.2.1
#> sys_3.4.3 systemfonts_1.2.3 textshaping_1.0.1 tibble_3.3.0
#> tidyr_1.3.1 tidyselect_1.2.1 tinytex_0.57 tools_4.5.1
#> utf8_1.2.6 utils_4.5.1 vctrs_0.6.5 viridisLite_0.4.2
#> withr_3.0.2 xfun_0.53 xml2_1.4.0 yaml_2.3.10