{highcharter} and the accessibility module: Part 5
R
data visualization
accessibility
highcharter
Highcharts
The final installment of our series on using the Highcharts accessibility module with {highcharter}, in which we re-create an area chart with lots of annotations.
Author
Mara Averick
Published
2021-11-16
This final installment in our series on using the Highcharts accessibility module with {highcharter} is basically “extra credit.” (Read: I took the time to figure this out, so I’m sharing it. But, it’s probably not the most helpful post if you’re just trying to get the accessibility module working with {highcharter}.)
Our {palmerpenguins} plot had a single annotation, which was recorded in the “Chart annotations summary” of the screen-reader section as an item in an unordered list.1
The chart we’re re-creating here (which plots elevation and distance of the 2017 Tour de France) has lots of annotations. We want these annotations to show up in the screen-reader section with the text itself and information about where it is on the chart (in terms and distance and elevation).
Up until now, we’ve been using elements of the Highcharts JavaScript API that are configured inside of Highcharts.chart({...});. For example, when we specify the valueSuffix for a tooltip using hc_tooltip(), we are referencing tooltip.valueSuffix, which is defined inside of Highcharts.chart({});.
However, there are some options that are defined outside of the chart object. These are set with Highcharts.setOptions({...});, and are divided up into global and lang. As it turns out, configuring accessibility strings takes place in lang.accessibility.
lang options
lang (AKA “the language object”) isn’t defined on a per-chart basis. As the Highcharts documentation describes:
The language object is global and it can’t be set on each chart initialization.
Once again, I made use of the Highcharts export module to get data out of the original chart in CSV format in order to read it back into R.
View code
library(tidyverse)url <-"https://gist.githubusercontent.com/batpigandme/1916d95323ddb29274bddf3316041fd3/raw/a8f394cb85b4ae995798c2596ffe912491d2fb7c/2017-tour-de-france-stage-8.csv"tdf_data <-read_csv(url) |>drop_na() # have some from data export
In the original the screen-reader text for annotations is set using the following code:
The “rules” (gleaned largely from the pattern-fills examples in the Modules & plugins vignette) are basically the same for the language object as they were for chart objects: each level of the JavaScript API becomes a nested list in your R code. The difference here is that we’re going to define them using options().
highchart() |>hc_add_dependency(name ="modules/accessibility.js") |>hc_add_dependency(name ="modules/annotations.js") |>hc_add_dependency(name ="modules/exporting.js") |>hc_add_dependency(name ="modules/export-data.js") |>hc_add_series(tdf_data, "area", hcaes(x = Distance, y = Elevation),lineColor ="#434348",color ="#90ed7d",fillOpacity =0.5,marker =list(enabled =FALSE)) |>hc_xAxis(title =list(text ="Distance"),labels =list(format ="{value} km"),minRange =5,accessibility =list(rangeDescription ="Range: 0 to 187.8 km." ) ) |>hc_yAxis(title =list(text =""),labels =list(format ="{value} m"),startOnTick =TRUE,endOnTick =FALSE,maxPadding =0.35,accessibility =list(description ="Elevation",rangeDescription ="Range: 0 to 1,553 meters" ) ) |>hc_title(text ="2017 Tour de France Stage 8: Dole - Station des Rousses") |>hc_caption(text ="An annotated line chart illustrates the 8th stage of the 2017 Tour de France cycling race from the start point in Dole to the finish line at Station des Rousses. Altitude is plotted on the Y-axis, and distance is plotted on the X-axis. The line graph is interactive, and the user can trace the altitude level along the stage. The graph is shaded below the data line to visualize the mountainous altitudes encountered on the 187.5-kilometre stage. The three largest climbs are highlighted at Col de la Joux, Côte de Viry and the final 11.7-kilometer, 6.4% gradient climb to Montée de la Combe de Laisia Les Molunes which peaks at 1200 meters above sea level. The stage passes through the villages of Arbois, Montrond, Bonlieu, Chassal and Saint-Claude along the route.") |># begin annotations# note that the points are grouped into separate lists# so that they can be styled in those groupshc_annotations(list(labelOptions =list(backgroundColor ='rgba(255,255,255,0.6)',verticalAlign ='top',y =15 ),labels =list(list(point =list(xAxis =0, yAxis =0, x =27.98, y =255),text ="Arbois" ),list(point =list(xAxis =0, yAxis =0, x =45.5, y =611),text ="Montrond" ),list(point =list(xAxis =0, yAxis =0, x =63, y =651),text ="Mont-sur-Monnet" ),list(point =list(xAxis =0, yAxis =0, x =84, y =789),x =-10,text ="Bonlieu" ),list(point =list(xAxis =0, yAxis =0, x =129.5, y =382),text ="Chassal" ),list(point =list(xAxis =0, yAxis =0, x =159, y =443),text ="Saint-Claude" ) ) ),list(labels =list(list(point =list(xAxis =0, yAxis =0, x =101.44, y =1026),x =-30,text ="Col de la Joux" ),list(point =list(xAxis =0, yAxis =0, x =138.5, y =748),text ="Côte de Viry" ),list(point =list(xAxis =0, yAxis =0, x =176.4, y =1202),text ="Montée de la Combe <br>de Laisia Les Molunes" ) ) ),list(labelOptions =list(shape ="connector",align ="right",justify =FALSE,crop =TRUE,style =list(fontSize ="0.8em",textOutline ="1px white" ) ),labels =list(list(point =list(xAxis =0, yAxis =0, x =96.2, y =783),text ="6.1 km climb <br>4.6% on avg." ),list(point =list(xAxis =0, yAxis =0, x =134.5, y =540),text ="7.6 km climb <br>5.2% on avg." ),list(point =list(xAxis =0, yAxis =0, x =172.2, y =925),text ="11.7 km climb <br>6.4% on avg." ) ) ) ) |>hc_tooltip(headerFormat ="Distance: {point.x:.1f} km<br>",pointFormat ="{point.y} m a. s. l.",shared =TRUE ) |>hc_legend(enabled =FALSE) |>hc_exporting(enabled =TRUE,accessibility =list(enabled =TRUE ) ) |>hc_plotOptions(accessibility =list(enabled =TRUE,keyboardNavigation =list(enabled =TRUE),linkedDescription ='This line chart uses the Highcharts Annotations feature to place labels at various points of interest. The labels are responsive and will be hidden to avoid overlap on small screens. Image description: An annotated line chart illustrates the 8th stage of the 2017 Tour de France cycling race from the start point in Dole to the finish line at Station des Rousses. Altitude is plotted on the Y-axis, and distance is plotted on the X-axis. The line graph is interactive, and the user can trace the altitude level along the stage. The graph is shaded below the data line to visualize the mountainous altitudes encountered on the 187.5-kilometre stage. The three largest climbs are highlighted at Col de la Joux, Côte de Viry and the final 11.7-kilometer, 6.4% gradient climb to Montée de la Combe de Laisia Les Molunes which peaks at 1200 meters above sea level. The stage passes through the villages of Arbois, Montrond, Bonlieu, Chassal and Saint-Claude along the route.',landmarkVerbosity ="one" ),area =list(accessibility =list(description ="This line chart uses the Highcharts Annotations feature to place labels at various points of interest. The labels are responsive and will be hidden to avoid overlap on small screens. Image description: An annotated line chart illustrates the 8th stage of the 2017 Tour de France cycling race from the start point in Dole to the finish line at Station des Rousses. Altitude is plotted on the Y-axis, and distance is plotted on the X-axis. The line graph is interactive, and the user can trace the altitude level along the stage. The graph is shaded below the data line to visualize the mountainous altitudes encountered on the 187.5-kilometre stage. The three largest climbs are highlighted at Col de la Joux, Côte de Viry and the final 11.7-kilometer, 6.4% gradient climb to Montée de la Combe de Laisia Les Molunes which peaks at 1200 meters above sea level. The stage passes through the villages of Arbois, Montrond, Bonlieu, Chassal and Saint-Claude along the route." ) ) )
Did it work?
The proof is in the pudding, which, in our case, is the HTML. Behold! We get the following at the end of our screen-reader section (but with all of the annotations in place of ...):
HTML
<div>"Chart annotations summary"<ul style="list-style-type: none"><li>Arbois, at distance 27.98km, elevation 255 meters.</li><li>Montrond, at distance 45.5km, elevation 611 meters.</li> ...</ul></div>
Wrapping up
Though I haven’t figured out how to get all of the aspects of the Highcharts accessibility module working from {highcharter} (linkedDescription, I’m looking at you), I’m confident that the issue is with the user (yours truly) and is not some limitation of Joshua Kunst’s excellent R package.
There’s only so much that can (or should) be automatically generated when it comes to making a data visualization accessible, but Highcharts gives you some really nice scaffolding to work with.
Lundgard, Alan, and Arvind Satyanarayan. 2022. “Accessible Visualization via Natural Language Descriptions: A Four-Level Model of Semantic Content.”IEEE Transactions on Visualization & Computer Graphics (Proceedings of IEEE VIS). https://doi.org/10.1109/TVCG.2021.3114770.
Footnotes
That’s a mouthful—summary point: if you didn’t read part 4, go skim the screen-reader-div section of it to make better sense of this post.↩︎