ggplot()
: GO0
ggplot()
: GO0
Objectives
For this chapter I have two objectives for my RWB project:
I will experiment with the basics of building dynamic interactive line charts. This goal includes:
ggplotly()
(see Section 8.1.2).ggplotly()
in {shiny} (see Section 8.1.4.1).plot_ly()
function in {shiny} (see Section 8.1.4.2).actionButton()
(see Section 8.2.2).The final figure with immediate drawing each selected countries includes some simplification and the solution to my colorscale problem (see Section 8.2.3).
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 8.4. 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 8.2 : PO0
: Line chart with ggplot()
and ggplotly()
(without Shiny).
ggplot()
and ggplotly()
Development of the global World Press Freedom Index (WPFI) of Austria with ggplot()
and ggplotly()
: GO0
R Code 8.3 : 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 8.2 without Shiny. But there are other two additions necessary:plotOutput()
to plotlyOutput()
and torenderPlot()
to renderPlotly()
.R Code 8.5 : PWS
Choose country to display WPFI for all available years with ggplotly()
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 800
#| components: [editor, viewer]
#| layout: vertical
## file: app.R
## app-061-line-chart-pws-1a
## Choose country to display WPFI for all available years
## Using ggplotly()
## @cnj-061-ine-chart-pws-1a
suppressWarnings(suppressPackageStartupMessages({
library(shiny)
library(bslib)
library(ggplot2)
library(dplyr)
library(plotly)
}))
rwb <- readRDS(gzcon(url("https://raw.githubusercontent.com/petzi53/rwb-book/master/data/chap011/rwb/rwb.rds")))
theme_set(theme_bw())
ui <- page_sidebar(
titlePanel("Evolution of the World Press Freedom Index (WPFI) 2013-2025"),
sidebar = sidebar(
selectInput(
inputId = "country",
label = "Country",
choices = unique(rwb$country_en)
)
),
card(
card_header((textOutput("card_title"))),
plotlyOutput("p") # (1)
)
)
server <- function(input, output, session) {
output$card_title <- renderText({
paste("World Prees Freedom Index for", input$country)
})
output$p <- renderPlotly({ # (2)
rwb <- rwb |>
select(year_n, country_en, score) |>
filter(country_en == input$country) |>
na.omit() |>
ggplot(aes(year_n, score)) +
geom_line() +
geom_point()
ggplotly(rwb)
})
}
shinyApp(ui, server)
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 8.6 : PWS
Choose country to display WPFI for all available years with plot_ly()
.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 800
#| components: [editor, viewer]
#| layout: vertical
## file: app.R
## app-061-line-chart-pws-1b
## Choose country to display WPFI for all available years
## Using native plot_ly()
## @cnj-061-ine-chart-pws-1b
suppressWarnings(suppressPackageStartupMessages({
library(shiny)
library(bslib)
library(dplyr)
library(plotly)
}))
rwb <- readRDS(gzcon(url("https://raw.githubusercontent.com/petzi53/rwb-book/master/data/chap011/rwb/rwb.rds")))
ui <- page_sidebar(
titlePanel("Evolution of the World Press Freedom Index (WPFI) 2013-2025"),
sidebar = sidebar(
selectInput(
inputId = "country",
label = "Country",
choices = unique(rwb$country_en)
)
),
card(
card_header((textOutput("card_title"))),
plotlyOutput("p")
)
)
server <- function(input, output, session) {
output$card_title <- renderText({
paste("World Prees Freedom Index for", input$country)
})
output$p <- renderPlotly({
rwb |>
select(year_n, country_en, score) |>
filter(country_en == input$country) |>
na.omit() |>
plot_ly(
x = ~year_n,
y = ~score,
type = 'scatter',
mode = 'lines+markers')
})
}
shinyApp(ui, server)
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 8.3, while mode = "lines+markers"
adds both lines connecting the points and markers at each point as in R Code 8.6.Resource 8.1 : How to build line and scatter plots with {plotly}
scatter
type has a rich set of customization options.R Code 8.7 : Show development of WPFI for several countries at once
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 800
#| components: [editor, viewer]
#| layout: vertical
## file: app.R
## app-061-line-chart-pws-2
## Choose several countries to display WPFI for all available years
## @cnj-061-ine-chart-pws-2
suppressWarnings(suppressPackageStartupMessages({
library(shiny)
library(bslib)
library(dplyr)
library(plotly)
}))
rwb <- readRDS(gzcon(url("https://raw.githubusercontent.com/petzi53/rwb-book/master/data/chap011/rwb/rwb.rds")))
ui <- page_sidebar(
titlePanel("Evolution of the World Press Freedom Index (WPFI) 2013-2025"),
sidebar = sidebar(
selectInput(
inputId = "country",
label = "Choose countries",
choices = unique(rwb$country_en),
multiple = TRUE
)
),
card(
card_header((textOutput("card_title"))),
plotlyOutput("p")
)
)
server <- function(input, output, session) {
output$card_title <- renderText({
my_countries <- filter(countries(), country_en %in% input$country)
txt <- unique(my_countries$country_en)
s = paste("World Press Freedom Index:", txt[1])
if (length(txt) > 1) {
for (i in 2:length(txt)) {
s <- paste(s, txt[i], sep = ", ")
}
}
s
})
countries <- reactive({
req(input$country)
rwb |>
select(year_n, score, country_en) |>
filter(country_en %in% input$country) |>
arrange(year_n) |>
na.omit() |>
droplevels()
})
output$p <- renderPlotly({
req(countries())
plotly::plot_ly(
data = countries(),
x = ~year_n,
y = ~score,
color = ~country_en,
colors = RColorBrewer::brewer.pal(12, "Paired"),
type = 'scatter',
mode = 'lines+markers',
marker = list(size = 10)
)
})
}
shinyApp(ui, server)
There are several important comments to make:
At first I tried to disntinguish between the first trace (with plot_ly()
) and all the other traces with add_trace()
. But it turned out that I just need to set the argument color
to the country vector.
I had to adapt the card title so that it can display all names of the displayed countries.
A big drawback is that the line color of the already chosen countries changes after another country is selected. So far I couldn’t find a solution. After my question was in StackOverflow not accepted (supposedly because it is a duplicate of another question), I posted in the Posit Forum for help.
I finally found the solution to my problem of changing the line color of the already chosen countries. The part I didn’t understand was that I needed a named color vector as demonstrated in the second example of Custom Color Scales of the Plotly website.
I didn’t apply the second part with setNames()
to the color palette pal
. A loop — as I thought — is not necessary.
I have commented the three added lines and also the one changed line. Additionally I have simplified the card_title()
rendering function and moved under the final renderPlotly()
function.
R Code 8.9 : Show development of WPFI with consistent colored country lines
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 800
#| components: [editor, viewer]
#| layout: vertical
## file: app.R
## app-061-line-chart-pws-solution
## Choose several countries to display WPFI for all available years
## @cnj-061-ine-chart-pws-solution
suppressWarnings(suppressPackageStartupMessages({
library(shiny)
library(bslib)
library(dplyr)
library(plotly)
}))
rwb <- readRDS(gzcon(url("https://raw.githubusercontent.com/petzi53/rwb-book/master/data/chap011/rwb/rwb.rds")))
ui <- page_sidebar(
titlePanel("Evolution of the World Press Freedom Index (WPFI) 2013-2025"),
sidebar = sidebar(
selectInput(
inputId = "country",
label = "Choose countries",
choices = unique(rwb$country_en),
multiple = TRUE
)
),
card(
card_header((textOutput("card_title"))),
plotlyOutput("p")
)
)
server <- function(input, output, session) {
pal = RColorBrewer::brewer.pal(12, "Paired") # added
countries <- reactive({
req(input$country)
# transferred and simplified #######################
output$card_title <- renderText({
s = paste(
"World Press Freedom Index for",
input$country[1]
)
if (length(input$country) > 1) {
for (i in 2:length(input$country)) {
s <- paste(s, input$country[i], sep = ", ")
}
}
s
})
#####################################################
rwb |>
select(year_n, score, country_en) |>
filter(country_en %in% input$country) |>
arrange(year_n) |>
na.omit() |>
droplevels()
})
output$p <- renderPlotly({
req(countries())
length(pal) <- length(input$country) # added
pal <- setNames(pal, input$country) # added
plotly::plot_ly(
data = countries(),
x = ~year_n,
y = ~score,
color = ~country_en,
colors = pal, # changed
type = 'scatter',
mode = 'lines+markers',
marker = list(size = 10)
)
})
}
shinyApp(ui, server)
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. |
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 dichromat_2.0-0.1
#> 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.9003 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 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.0
#> 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