├── README.md
├── includes
└── export-csv.js
├── highcharts-accessible.js
└── example.html
/README.md:
--------------------------------------------------------------------------------
1 | # highcharts-accessible
2 | This plugin improves accessibility of the graphs rendered by Highcharts.
3 | Full documenation at http://seth-myers.github.io/highcharts-accessible/
4 | Live example at http://seth-myers.com/highcharts-accessible/
5 |
6 | ## About
7 | **This only works with bar, column, line and pie charts so far. Will be adding more chart types later.**
8 |
9 | It works by automatically applying three best practices for accessible SVGs.
10 |
11 | 1. Automatically creates and inserts intelligent title and description tags.
12 | 2. Inserts ARIA attributes and language definition in the SVG to further optimize the chart for screen readers
13 | 3. Automatically inserts a link to a downloadable CSV of the data.
14 |
15 | Highchart SVG rendered **without** the plugin:
16 | 
17 |
18 | Highchart SVG rendered **with** the plugin:
19 | 
20 |
21 | ## Dependency
22 | Include the [export-csv.js](http://www.highcharts.com/plugin-registry/single/7/Export%20Data "export-csv.js download page") plugin to enable the CSV functionality.
23 |
24 | ## Installation
25 | Import this plugin after `highcharts.js` and `export-csv.js`, like this:
26 | ```
27 |
28 |
29 |
30 | ```
31 | ## Usage
32 | All parameters are optional, but `accessibleSettings: {}` must be called in order to run the plugin.
33 | ```javascript
34 | $("#container").highcharts({
35 | accessibleSettings: {
36 | ```
37 |
38 | > **autoAccessible: Boolean**
39 | > Allow a summary of the chart to be written automatically. This is the primary purpose of the plugin. If set to false, a user-defined title and description must be provided. Defaults to `true`.
40 |
41 | > **csvLinkText: String**
42 | > Text used in link to download a CSV file. If you have more than one highchart SVG on a page, it is important to add text that helps users associate the link with the chart. Defaults to “Download chart data.”.
43 |
44 | > **langAttribute: String**
45 | > Change the language attribute for the SVG. [More information](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xml:lang "Mozilla SVG lang") on lang attribute. Defaults to “en” for English.
46 |
47 | > **timeSeries:Boolean**
48 | > Indicate if the dataset has a time element. This affects how the autoAccessible feature process the text. Defaults to `true`.
49 |
50 | > **userDesc: String**
51 | > Text to be used in the SVG’s description tag. This will override the autoAccessible feature. I recommend using this option and writing your own summary of what the chart shows. It will likely be more readable than what the autoAccessible script can generate. Defaults to `null`.
52 |
53 | > **userTitle: String**
54 | > Text to be used in the SVG’s title tag. If this is tag is not used, access will use the text in highchart’s title.text in the SVG title tag. If that is empty, it defaults to "This is a data visualization. Please see the following description tag for a synopsis of what it shows.".
55 |
56 | > **xUnitPrefix: String**
57 | > Text to prepend to the x value in the description tag. Defaults to empty string.
58 |
59 | > **xUnitSuffix: String**
60 | > Text to append to the x value in the description tag. Defaults to empty string.
61 |
62 | > **yUnitPrefix: String**
63 | > Text to prepend to the y value in the description tag. Defaults to empty string.
64 |
65 | > **yUnitSuffix: String**
66 | > Text to append to the y value in the description tag. Defaults to empty string.
67 |
68 | ```javascript
69 | }
70 | });
71 | ```
72 |
73 | *"Inclusive design is good design."*
74 |
--------------------------------------------------------------------------------
/includes/export-csv.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A Highcharts plugin for exporting data from a rendered chart as CSV, XLS or HTML table
3 | *
4 | * Author: Torstein Honsi
5 | * Licence: MIT
6 | * Version: 1.4.1
7 | */
8 | /*global Highcharts, window, document, Blob */
9 | (function (factory) {
10 | if (typeof module === 'object' && module.exports) {
11 | module.exports = factory;
12 | } else {
13 | factory(Highcharts);
14 | }
15 | })(function (Highcharts) {
16 |
17 | 'use strict';
18 |
19 | var each = Highcharts.each,
20 | pick = Highcharts.pick,
21 | downloadAttrSupported = document.createElement('a').download !== undefined;
22 |
23 | Highcharts.setOptions({
24 | lang: {
25 | downloadCSV: 'Download CSV',
26 | downloadXLS: 'Download XLS',
27 | viewData: 'View data table'
28 | }
29 | });
30 |
31 |
32 | /**
33 | * Get the data rows as a two dimensional array
34 | */
35 | Highcharts.Chart.prototype.getDataRows = function () {
36 | var options = (this.options.exporting || {}).csv || {},
37 | xAxis = this.xAxis[0],
38 | rows = {},
39 | rowArr = [],
40 | dataRows,
41 | names = [],
42 | i,
43 | x,
44 | xTitle = xAxis.options.title && xAxis.options.title.text,
45 |
46 | // Options
47 | dateFormat = options.dateFormat || '%Y-%m-%d %H:%M:%S',
48 | columnHeaderFormatter = options.columnHeaderFormatter || function (series, key, keyLength) {
49 | return series.name + (keyLength > 1 ? ' ('+ key + ')' : '');
50 | };
51 |
52 | // Loop the series and index values
53 | i = 0;
54 | each(this.series, function (series) {
55 | var keys = series.options.keys,
56 | pointArrayMap = keys || series.pointArrayMap || ['y'],
57 | valueCount = pointArrayMap.length,
58 | requireSorting = series.requireSorting,
59 | categoryMap = {},
60 | j;
61 |
62 | // Map the categories for value axes
63 | each(pointArrayMap, function (prop) {
64 | categoryMap[prop] = (series[prop + 'Axis'] && series[prop + 'Axis'].categories) || [];
65 | });
66 |
67 | if (series.options.includeInCSVExport !== false && series.visible !== false) { // #55
68 | j = 0;
69 | while (j < valueCount) {
70 | names.push(columnHeaderFormatter(series, pointArrayMap[j], pointArrayMap.length));
71 | j = j + 1;
72 | }
73 |
74 | each(series.points, function (point, pIdx) {
75 | var key = requireSorting ? point.x : pIdx,
76 | prop,
77 | val;
78 |
79 | j = 0;
80 |
81 | if (!rows[key]) {
82 | rows[key] = [];
83 | }
84 | rows[key].x = point.x;
85 |
86 | // Pies, funnels etc. use point name in X row
87 | if (!series.xAxis) {
88 | rows[key].name = point.name;
89 | }
90 |
91 | while (j < valueCount) {
92 | prop = pointArrayMap[j]; // y, z etc
93 | val = point[prop];
94 | rows[key][i + j] = pick(categoryMap[prop][val], val); // Pick a Y axis category if present
95 | j = j + 1;
96 | }
97 |
98 | });
99 | i = i + j;
100 | }
101 | });
102 |
103 | // Make a sortable array
104 | for (x in rows) {
105 | if (rows.hasOwnProperty(x)) {
106 | rowArr.push(rows[x]);
107 | }
108 | }
109 | // Sort it by X values
110 | rowArr.sort(function (a, b) {
111 | return a.x - b.x;
112 | });
113 |
114 | // Add header row
115 | if (!xTitle) {
116 | xTitle = xAxis.isDatetimeAxis ? 'DateTime' : 'Category';
117 | }
118 | dataRows = [[xTitle].concat(names)];
119 |
120 | // Transform the rows to CSV
121 | each(rowArr, function (row) {
122 |
123 | var category = row.name;
124 | if (!category) {
125 | if (xAxis.isDatetimeAxis) {
126 | category = Highcharts.dateFormat(dateFormat, row.x);
127 | } else if (xAxis.categories) {
128 | category = pick(xAxis.names[row.x], xAxis.categories[row.x], row.x)
129 | } else {
130 | category = row.x;
131 | }
132 | }
133 |
134 | // Add the X/date/category
135 | row.unshift(category);
136 | dataRows.push(row);
137 | });
138 |
139 | return dataRows;
140 | };
141 |
142 | /**
143 | * Get a CSV string
144 | */
145 | Highcharts.Chart.prototype.getCSV = function (useLocalDecimalPoint) {
146 | var csv = '',
147 | rows = this.getDataRows(),
148 | options = (this.options.exporting || {}).csv || {},
149 | itemDelimiter = options.itemDelimiter || ',', // use ';' for direct import to Excel
150 | lineDelimiter = options.lineDelimiter || '\n'; // '\n' isn't working with the js csv data extraction
151 |
152 | // Transform the rows to CSV
153 | each(rows, function (row, i) {
154 | var val = '',
155 | j = row.length,
156 | n = useLocalDecimalPoint ? (1.1).toLocaleString()[1] : '.';
157 | while (j--) {
158 | val = row[j];
159 | if (typeof val === "string") {
160 | val = '"' + val + '"';
161 | }
162 | if (typeof val === 'number') {
163 | if (n === ',') {
164 | val = val.toString().replace(".", ",");
165 | }
166 | }
167 | row[j] = val;
168 | }
169 | // Add the values
170 | csv += row.join(itemDelimiter);
171 |
172 | // Add the line delimiter
173 | if (i < rows.length - 1) {
174 | csv += lineDelimiter;
175 | }
176 | });
177 | return csv;
178 | };
179 |
180 | /**
181 | * Build a HTML table with the data
182 | */
183 | Highcharts.Chart.prototype.getTable = function (useLocalDecimalPoint) {
184 | var html = '
',
185 | rows = this.getDataRows();
186 |
187 | // Transform the rows to HTML
188 | each(rows, function (row, i) {
189 | var tag = i ? 'td' : 'th',
190 | val,
191 | j,
192 | n = useLocalDecimalPoint ? (1.1).toLocaleString()[1] : '.';
193 |
194 | html += '
';
195 | for (j = 0; j < row.length; j = j + 1) {
196 | val = row[j];
197 | // Add the cell
198 | if (typeof val === 'number') {
199 | val = val.toString();
200 | if (n === ',') {
201 | val = val.replace('.', n);
202 | }
203 | html += '<' + tag + ' class="number">' + val + '' + tag + '>';
204 |
205 | } else {
206 | html += '<' + tag + '>' + (val === undefined ? '' : val) + '' + tag + '>';
207 | }
208 | }
209 |
210 | html += '
';
211 | });
212 | html += '
';
213 | return html;
214 | };
215 |
216 | function getContent(chart, href, extension, content, MIME) {
217 | var a,
218 | blobObject,
219 | name,
220 | options = (chart.options.exporting || {}).csv || {},
221 | url = options.url || 'http://www.highcharts.com/studies/csv-export/download.php';
222 |
223 | if (chart.options.exporting.filename) {
224 | name = chart.options.exporting.filename;
225 | } else if (chart.title) {
226 | name = chart.title.textStr.replace(/ /g, '-').toLowerCase();
227 | } else {
228 | name = 'chart';
229 | }
230 |
231 | // MS specific. Check this first because of bug with Edge (#76)
232 | if (window.Blob && window.navigator.msSaveOrOpenBlob) {
233 | // Falls to msSaveOrOpenBlob if download attribute is not supported
234 | blobObject = new Blob([content]);
235 | window.navigator.msSaveOrOpenBlob(blobObject, name + '.' + extension);
236 |
237 | // Download attribute supported
238 | } else if (downloadAttrSupported) {
239 | a = document.createElement('a');
240 | a.href = href;
241 | a.target = '_blank';
242 | a.download = name + '.' + extension;
243 | document.body.appendChild(a);
244 | a.click();
245 | a.remove();
246 |
247 | } else {
248 | // Fall back to server side handling
249 | Highcharts.post(url, {
250 | data: content,
251 | type: MIME,
252 | extension: extension
253 | });
254 | }
255 | }
256 |
257 | /**
258 | * Call this on click of 'Download CSV' button
259 | */
260 | Highcharts.Chart.prototype.downloadCSV = function () {
261 | var csv = this.getCSV(true);
262 | getContent(
263 | this,
264 | 'data:text/csv,' + csv.replace(/\n/g, '%0A'),
265 | 'csv',
266 | csv,
267 | 'text/csv'
268 | );
269 | };
270 |
271 | /**
272 | * Call this on click of 'Download XLS' button
273 | */
274 | Highcharts.Chart.prototype.downloadXLS = function () {
275 | var uri = 'data:application/vnd.ms-excel;base64,',
276 | template = '' +
277 | '' +
280 | '' +
281 | '' +
282 | '' +
283 | this.getTable(true) +
284 | '',
285 | base64 = function (s) {
286 | return window.btoa(unescape(encodeURIComponent(s))); // #50
287 | };
288 | getContent(
289 | this,
290 | uri + base64(template),
291 | 'xls',
292 | template,
293 | 'application/vnd.ms-excel'
294 | );
295 | };
296 |
297 | /**
298 | * View the data in a table below the chart
299 | */
300 | Highcharts.Chart.prototype.viewData = function () {
301 | if (!this.insertedTable) {
302 | var div = document.createElement('div');
303 | div.className = 'highcharts-data-table';
304 | // Insert after the chart container
305 | this.renderTo.parentNode.insertBefore(div, this.renderTo.nextSibling);
306 | div.innerHTML = this.getTable();
307 | this.insertedTable = true;
308 | }
309 | };
310 |
311 |
312 | // Add "Download CSV" to the exporting menu. Use download attribute if supported, else
313 | // run a simple PHP script that returns a file. The source code for the PHP script can be viewed at
314 | // https://raw.github.com/highslide-software/highcharts.com/master/studies/csv-export/csv.php
315 | if (Highcharts.getOptions().exporting) {
316 | Highcharts.getOptions().exporting.buttons.contextButton.menuItems.push({
317 | textKey: 'downloadCSV',
318 | onclick: function () { this.downloadCSV(); }
319 | }, {
320 | textKey: 'downloadXLS',
321 | onclick: function () { this.downloadXLS(); }
322 | }, {
323 | textKey: 'viewData',
324 | onclick: function () { this.viewData(); }
325 | });
326 | }
327 |
328 | });
329 |
--------------------------------------------------------------------------------
/highcharts-accessible.js:
--------------------------------------------------------------------------------
1 | // Accessible SVG resources
2 | // http://www.w3.org/TR/SVG/access.html#SVGAccessibilityGuidelines
3 | // http://www.idpf.org/accessibility/guidelines/
4 |
5 | // TODO
6 | // add ONKEYPRESS handler if ONCLICK hanlder was added by highcharts (Not a normal function of highcharts, but it can be done.)
7 | // add autoAccessible functinality for remaining charts
8 | (function(H) {
9 |
10 | H.wrap(H.Chart.prototype, 'init', function(proceed) {
11 |
12 | // Do not run accesssibility if it wasn't setup by user
13 | if (!arguments[1].accessibleSettings) {
14 | proceed.apply(this, Array.prototype.slice.call(arguments, 1));
15 | return;
16 | }
17 |
18 | // set options
19 | var accOptions = {
20 | approvedTypes: ['bar', 'column', 'line', 'pie'],
21 | autoAccessible: (arguments[1].accessibleSettings.autoAccessible === undefined) ? true : arguments[1].accessibleSettings.autoAccessible, // default to auto-accessible feature on
22 | chartType: findChartTypes(arguments[1]),
23 | csvLinkText: arguments[1].accessibleSettings.csvLinkText || "Download chart data.",
24 | langAttribute: arguments[1].accessibleSettings.langAttribute || "en", // default to english,
25 | timeSeries: (arguments[1].accessibleSettings.timeSeries === undefined) ? true : arguments[1].accessibleSettings.timeSeries, // default to time series
26 | userDesc: arguments[1].accessibleSettings.desc || null,
27 | userTitle: arguments[1].accessibleSettings.title || null,
28 | xUnitPrefix: arguments[1].accessibleSettings.xUnitPrefix || "",
29 | xUnitSuffix: arguments[1].accessibleSettings.xUnitSuffix || "",
30 | yUnitPrefix: arguments[1].accessibleSettings.yUnitPrefix || "",
31 | yUnitSuffix: arguments[1].accessibleSettings.yUnitSuffix || ""
32 | };
33 |
34 |
35 |
36 | // Ensure title and desc text are entered if the autoAccessible feature is off.
37 | if (!accOptions.autoAccessible && !accOptions.userTitle || !accOptions.autoAccessible && !accOptions.userDesc) {
38 | throw "highcharts-accessible plugin error: A description and title must be provided if the autoAccessible feature is set to false.";
39 | }
40 |
41 | // Let highcharts make the chart.
42 | proceed.apply(this, Array.prototype.slice.call(arguments, 1));
43 |
44 | // Add manually-entered title and desc tags if autoAccessible feature is off.
45 | if (!accOptions.autoAccessible) {
46 | setTags(accOptions.userTitle, accOptions.userDesc, this, accOptions);
47 | return;
48 | }
49 |
50 | var titleToPost = processTitle(accOptions, this);
51 | var descToPost = processDesc(accOptions, this);
52 |
53 | // add the new tags and ARIA attributes
54 | setTags(titleToPost, descToPost, this, accOptions);
55 |
56 | // Add CSV download, if dependency not available suggest user adds export-csv.js to automatically allow for CSV download
57 | if (typeof this.getCSV !== 'function') {
58 | console.warn('highcharts-accessible plugin warning: please load the export-csv.js plugin to enable CSV download functionality: http://www.highcharts.com/plugin-registry/single/7/Export%20Data ');
59 | } else {
60 | csvDownload(this.getCSV(), this.renderTo.id, accOptions);
61 | };
62 |
63 | });
64 |
65 | // return string for the description tag
66 | // in highcharts, data is generally y-vals and dates/labels are x-vals
67 | function processDesc(o, d) {
68 |
69 | // return user-supplied desc to access if available
70 | if (o.userDesc) {
71 | return o.userDesc;
72 | }
73 |
74 | var theReturn = "";
75 | var series = d.series;
76 |
77 | // number of data points combined.
78 | // This is used to keep the descriptions from getting too long.
79 | var combinedSeriesLen = series[0].data.length * series.length;
80 |
81 | for (var i = 0; i < series.length; i++) {
82 | var s = series[i];
83 | var seriesType = o.chartType[i];
84 | var xVals = s.processedXData;
85 | var xValsLen = xVals.length;
86 | var xLabels = get(s, 'xAxis.categories')
87 | var yVals = s.processedYData;
88 | var yValsLen = yVals.length;
89 | var yValsHighLow = getHighLow(yVals); // use this to build the bar time series chart, say which date is highest/lowest for each data series
90 | var sName = s.name;
91 |
92 | // Warn if this chart type isn't currently supported.
93 | if (o.approvedTypes.indexOf(seriesType) < 0) {
94 | console.log("highcharts-accessible plugin error: This chart type is still in development. Access can only process " + o.approvedTypes.toString() + " charts. This data series will not be added to the description.");
95 | continue;
96 | }
97 |
98 | // These build the string to place into the desc tag
99 | // format text for pie chart, describe each series
100 | if (seriesType == 'pie') {
101 | var total = yVals.reduce(function(a, b) {
102 | return a + b;
103 | });
104 | for (var ii = 0; ii < xValsLen; ii++) {
105 | theReturn += s.data[ii].name + " " + o.yUnitPrefix + yVals[ii] + o.yUnitSuffix + ", " + round((yVals[ii] / total * 100), 1) + "% of the total. ";
106 | }
107 | }
108 |
109 | if (o.timeSeries) {
110 | // format text for time series
111 | if (seriesType == 'line' || seriesType == 'bar' || seriesType == 'column') {
112 | theReturn += sName + " " + getDirection(yVals[0], yVals[yValsLen - 1]) + " to " + o.yUnitPrefix + yVals[yValsLen - 1] + o.yUnitSuffix + " in " + o.xUnitPrefix + xVals[xValsLen - 1] + o.xUnitSuffix + " from " + o.yUnitPrefix + yVals[0] + o.yUnitSuffix + " in " + o.xUnitPrefix + xVals[0] + o.xUnitSuffix + ". " + sName + " peaked at " + o.yUnitPrefix + yValsHighLow.high[0] + o.yUnitSuffix + " in " + o.xUnitPrefix + xVals[yValsHighLow.high[1]] + o.xUnitSuffix + ", and dipped lowest to " + o.yUnitPrefix + yValsHighLow.low[0] + o.yUnitSuffix + " in " + o.xUnitPrefix + xVals[yValsHighLow.low[1]] + ". ";
113 | }
114 | } else {
115 | // format text for nominal series
116 | // list all values if there are less than 15 data points in the whole chart
117 | if (combinedSeriesLen < 15) {
118 | if (seriesType == 'line' || seriesType == 'bar' || seriesType == 'column') {
119 | for (var ii = 0; ii < xValsLen; ii++) {
120 | theReturn += o.xUnitPrefix + yVals[ii] + o.xUnitSuffix + ", " + xLabels[ii] + ", " + sName + ". ";
121 | }
122 | }
123 | // otherwise just say the high, low for each series
124 | } else {
125 | if (seriesType == 'line' || seriesType == 'bar' || seriesType == 'column') {
126 | theReturn += sName + ", highest value of " + o.yUnitPrefix + yValsHighLow.high[0] + o.yUnitSuffix + " for " + xLabels[yValsHighLow.high[1]] + ". " + sName + ", lowest value of " + o.yUnitPrefix + yValsHighLow.low[0] + o.yUnitSuffix + " for " + xLabels[yValsHighLow.low[1]] + ". ";
127 | }
128 | }
129 | }
130 | }
131 |
132 | if (theReturn == "") {
133 | return "This chart format is not yet supported by the Accessible Highcharts plugin. Please see the link below to download the data.";
134 | } else {
135 | return theReturn;
136 | }
137 | }
138 |
139 | function processTitle(o, d) {
140 |
141 | // return user-supplied title to access if available
142 | if (o.userTitle) {
143 | return o.userTitle;
144 | }
145 |
146 | // if not, try to use the title given to highcharts.js
147 | var hgTitle = get(d, 'userOptions.title.text');
148 | if (hgTitle && hgTitle != "") {
149 | return hgTitle;
150 | }
151 |
152 | // otherwise say its a chart and direct folks to the description tag
153 | return "This is a data visualization. Please see the following description tag for a synopsis of what it shows.";
154 | }
155 |
156 | // Not sure if download attribute or opening new window is better
157 | // Both approaches seem to have the same result
158 | function csvDownload(d, chartID, userOptions) {
159 |
160 | // denote when the CSV file ends per 508-coordinator
161 | d += '\nEnd of File';
162 |
163 | var theSVG = document.getElementById(chartID);
164 | var newlinkNode = document.createElement("a");
165 | var newlinkContent = document.createTextNode(userOptions.csvLinkText);
166 | newlinkNode.appendChild(newlinkContent);
167 | newlinkNode.setAttribute("href", "data:text/csv;charset=utf-8," + escape(d));
168 | newlinkNode.setAttribute("download", "chart-data.csv");
169 | theSVG.appendChild(newlinkNode);
170 |
171 | // newlinkNode.onclick = function() {
172 | // //window.open( "data:text/csv;charset=utf-8," + escape(d) );
173 | // };
174 | }
175 |
176 | // get chart type for each series item
177 | // return array of chart types
178 | function findChartTypes(d) {
179 |
180 | var theReturn = [];
181 | var chartSeries = get(d, 'chart.type');
182 |
183 | for (var i = 0; i < d.series.length; i++) {
184 |
185 | var seriesString = 'd.series[' + i + '].type';
186 |
187 | if (get(d, seriesString)) {
188 | theReturn.push(d.series[i].type)
189 | } else if (chartSeries) {
190 | theReturn.push(chartSeries)
191 | } else {
192 | theReturn.push('line');
193 | }
194 |
195 | }
196 | return theReturn;
197 | }
198 |
199 | // return object of arrays high/low value and index position
200 | // { high: [val, index], low: [val, index] }
201 | function getHighLow(d) {
202 | var theReturn = {
203 | high: [d[0], 0],
204 | low: [d[0], 0]
205 | };
206 | for (var i = 0; i < d.length; i++) {
207 |
208 | if (d[i] > theReturn.high[0]) {
209 | theReturn.high = [d[i], i];
210 | }
211 | if (d[i] < theReturn.low[0]) {
212 | theReturn.low = [d[i], i];
213 | }
214 | }
215 | return theReturn;
216 | }
217 |
218 | function getDirection(s, e) {
219 | var equal;
220 | var text;
221 | if (s == e) {
222 | return 'did not change';
223 | } else {
224 | return s < e ? 'increased' : 'decreased';
225 | }
226 | }
227 |
228 | // http://stackoverflow.com/questions/23808928/javascript-elegant-way-to-check-nested-object-properties-for-null-undefined
229 | function get(obj, key) {
230 | return key.split(".").reduce(function(o, x) {
231 | return (typeof o == "undefined" || o === null) ? o : o[x];
232 | }, obj);
233 | }
234 |
235 | function round(num, places) {
236 | var multiplier = Math.pow(10, places);
237 | return Math.round(num * multiplier) / multiplier;
238 | }
239 |
240 | // add desc and title tags with ARIA attributres, add lang attibute to SVG
241 | function setTags(tTag, dTag, chartData, userOptions) {
242 |
243 | //console.log(userOptions);
244 |
245 | var chartID = chartData.renderTo.id;
246 | var theSVG = document.getElementById(chartID).firstChild.firstChild;
247 | var descNode = theSVG.firstChild;
248 |
249 | // add lang attribute to SVG tag
250 | theSVG.setAttribute("xml:lang", userOptions.langAttribute);
251 |
252 | // add ID to desc node for ARIA
253 | descNode.setAttribute("id", chartID + "-accessible-desc");
254 |
255 | descText = descNode.firstChild.nodeValue = dTag;
256 |
257 | var newTitleNode = document.createElement("title");
258 | var newTitleContent = document.createTextNode(tTag);
259 | newTitleNode.appendChild(newTitleContent);
260 |
261 | // add ID to title node for Aria
262 | newTitleNode.setAttribute("id", chartID + "-accessible-title");
263 |
264 | // insert the titgle tag at the beginning of the svg
265 | theSVG.insertBefore(newTitleNode, descNode);
266 |
267 | // set ARIA attributes on SVG tag
268 | theSVG.setAttribute("aria-labelledby", chartID + "-accessible-title");
269 | theSVG.setAttribute("aria-describedby", chartID + "-accessible-desc");
270 | }
271 |
272 | }(Highcharts));
273 |
--------------------------------------------------------------------------------
/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Accessibility Test for Highcharts Plugin
5 |
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
Accessibility Test for Highcharts Plugin
24 |
This plugin automatically adds intelligent title and description tags to SVG charts rendered by Highcharts. It also adds ARIA attributes to bolster accessibility, as well as dynamically creates a CSV file for download.