{higcharter} and the Highcharts accessibility module: Part 2
R
data visualization
accessibility
highcharter
Highcharts
Re-creating Highcharts’ demo accessible line chart using the {highcharter} R package, resulting in an interactive visualization with keyboard navigation and built-in features for screen readers.
As mentioned in the first part of this series, this chart was the first in my highcharter-accessibility experiments.1 Thus, it’s only in hindsight that I realize that it has some features that result in a less R-like syntax than is usually possible when using {highcharter}. So, we’ll go create two versions of the visualization: one that most closely resembles the JavaScript-API-based original, and one that (to me) felt more natural to write.
Laying the groundwork
Before you make a chart, you need data! Since the original version of the visualization uses the Highcharts export module, I was able to simply export the data as a CSV (which I then stashed in a Gist), and read the data back in using readr::read_csv().
Code
suppressPackageStartupMessages(library(tidyverse))library(highcharter)# read in the dataurl <-"https://gist.githubusercontent.com/batpigandme/aeb30566f899cdcdeb6024c0344d1ae1/raw/9cbafbbc99311c04b1a675e0ebb3953692fd51b8/pop-screenreaders.csv"raw_dat <-read_csv(url)
Without any modification, this is what the data (which I name raw_dat) looks like:
Line chart: original edition
Though this data isn’t “tidy” (more on that later), the format lends itself to a solid re-creation of the original version. So, all I did in terms of cleanup was remove the periods from the abbreviated months, and convert Category into an ordered factor.
Code
# turn Category into ordered factor and remove inconsistent periodsr_dat1 <- raw_dat |>mutate(Category =str_replace_all(Category, "\\.", "")) |>mutate(Category =as_factor(Category))
Because I was following the {highcharter} Modules & plugins vignette (particularly the one on using pattern fills), I made this chart in two parts: first setting up a base chart without the data, and then adding the data to it. I’m not sure this was necessary or helpful, but, since I didn’t take that approach with the other charts in this series, I’ll keep it here for variety’s sake.
I add the dependency modules first, though it works fine if you add them at the end too. If not for the JavaScript code, I wouldn’t have known that there was actually a series-label module to add, in addition to our export, export-data, and accessibility modules.
You’ll also notice that I seem to (and do) add the same text twice. I have yet to get the linkedDescription accessibility option to work correctly. However, it’s supposed to appear in a special, hidden div especially for screen readers that appears before the chart, along with much of the other metadata you see below.2
Code
hc_sr_setup <-highchart() |># add dependencieshc_add_dependency(name ="modules/series-label.js") |>hc_add_dependency(name ="modules/accessibility.js") |>hc_add_dependency(name ="modules/exporting.js") |>hc_add_dependency(name ="modules/export-data.js") |>hc_chart(type ="spline",accessibility =list(enabled =TRUE,keyboardNavigation =list(enabled =TRUE),linkedDescription ="Line chart demonstrating some accessibility features of Highcharts. The chart displays the most commonly used screen readers in surveys taken by WebAIM from December 2010 to September 2019. JAWS was the most used screen reader until 2019, when NVDA took over. VoiceOver is the third most used screen reader, followed by Narrator. ZoomText/Fusion had a surge in 2015, but usage is otherwise low. The overall use of other screen readers has declined drastically the past few years." ),dateTimeLabelFormats =list(month =list(main ="%b %Y") ) ) |>hc_title(text ="Most common desktop screen readers") |>hc_subtitle(text ="Source: WebAIM.") |>hc_caption(text ="Line chart demonstrating some accessibility features of Highcharts. The chart displays the most commonly used screen readers in surveys taken by WebAIM from December 2010 to September 2019. JAWS was the most used screen reader until 2019, when NVDA took over. VoiceOver is the third most used screen reader, followed by Narrator. ZoomText/Fusion had a surge in 2015, but usage is otherwise low. The overall use of other screen readers has declined drastically the past few years.") |>hc_xAxis(categories = sr_dat1$Category,title =list(text ="Time"),accesibility =list(enabled =TRUE,description ="Time from December 2010 to September 2019",range ="December 2010 to September 2019" ) ) |>hc_yAxis(title =list(text ="Percentage usage"),accessibility =list(description ="Percentage usage") ) |>hc_legend(symbolWidth =40) |>hc_plotOptions(spline =list(accessibility =list(enabled =TRUE,keyboardNavigation =list(enabled =TRUE) ) ) )
Now we need to add data! The following code is not very parsimonious. I add each screen reader as its own series (which is why I kept the data in its original format, with each screen reader represented by a column). This allows me to get dual encoding by manually specifying dashStyle and marker.symbol for each group in addition to colour.
I also try to give a description for the x-axis range again. This was another feature I wasn’t able to get working manually, but which works well when the range is numeric (which you’ll see in the second version of the chart).
The {higcharter} package was designed with a syntax meant to be “intuitive” to R users, in particular those familiar with {ggplot2}. For example, rather than making each screen reader its own series, each group could be defined in the hcaes() argument, which:
Define[s] aesthetic mappings. Similar in spirit to ggplot2::aes(Kunst 2021).
The downside of doing things this way is that you do not get the dual encoding for the splines (i.e. different line styles and different colours). The shapes for the points are different, you just don’t get to specify them manually (which isn’t a very big deal).
In order to use screen_reader as a grouping variable, I’ll have to reshape the data a bit. Since we’re mixing things up here, I also create a year variable, which means that Highcharts will be able to automatically generate a rangeDescription (since it’s numeric).
Code
# replace July with proper abbreviationraw_dat[4,1] <-"Jul 2015"# turn into "tidy" data format for categoriessr_dat2 <- raw_dat |>separate(Category, into =c("month", "year")) |>select(-month) |>pivot_longer(!year, names_to ="screen_reader", values_to ="pct_usage") |>mutate(year =parse_number(year)) |>drop_na()
The resulting data is longer (appropriate, since we used the pivot_longer() function), and looks like this:
Whereas before I used highchart() to set up the chart before adding data, this time I’ll send the data into hchart() from the start.
Code
hchart( sr_dat2,"spline",hcaes(x = year, y = pct_usage, group = screen_reader),accessibility =list(enabled =TRUE,keyboardNavigation =list(enabled =TRUE),linkedDescription ="Line chart demonstrating some accessibility features of Highcharts. The chart displays the most commonly used screen readers in surveys taken by WebAIM from December 2010 to September 2019. JAWS was the most used screen reader until 2019, when NVDA took over. VoiceOver is the third most used screen reader, followed by Narrator. ZoomText/Fusion had a surge in 2015, but usage is otherwise low. The overall use of other screen readers has declined drastically the past few years." ),exporting =list(enabled =TRUE,accessibility =list(enabled =TRUE) )) |>hc_title(text ="Most common desktop screen readers") |>hc_subtitle(text ="Source: WebAIM.") |>hc_caption(text ="Line chart demonstrating some accessibility features of Highcharts. The chart displays the most commonly used screen readers in surveys taken by WebAIM from December 2010 to September 2019. JAWS was the most used screen reader until 2019, when NVDA took over. VoiceOver is the third most used screen reader, followed by Narrator. ZoomText/Fusion had a surge in 2015, but usage is otherwise low. The overall use of other screen readers has declined drastically the past few years.") |>hc_xAxis(title =list(text ="Time"),accesibility =list(enabled =TRUE,description =list(text ="Time from December 2010 to September 2019"),rangeDescription ="2010 to 2019" ) ) |>hc_yAxis(title =list(text ="Percentage usage"),accessibility =list(description ="Percentage usage") ) |>hc_exporting(enabled =TRUE,accessibility =list(enabled =TRUE ) ) |>hc_tooltip(valueSuffix ="%") |>hc_legend(symbolWidth =40) |>hc_colors(c('#0b7383', '#6B26F0', '#76767A', '#4372da', '#222222', '#3D239E')) |>hc_add_dependency(name ="modules/accessibility.js") |>hc_add_dependency(name ="modules/exporting.js") |>hc_add_dependency(name ="modules/export-data.js") |>hc_add_dependency(name ="modules/series-label.js") |>hc_plotOptions(series =list(label =list(enabled =TRUE) ) )
As you can see, the charts are quite similar, and the aesthetics are defined in a way that would definitely be familiar to a ggplot2 user: hcaes(x = year, y = pct_usage, group = screen_reader).
Incidentally, if you view the chart as a data table (View data table is the last option in the export menu at the top right corner), the format is the same as it was for the original (i.e. the output data doesn’t look long).
Fin
Because I’ve made these charts over time, you’re likely to spot differences in syntax in addition to those I already pointed out. There are also some redundancies, which reflect my ongoing battle to get certain accessibility features working when manually defined. Any corrections and suggestions are welcome!