From 0e46d3ec42704a61c679dbc3915a286d258ba3b5 Mon Sep 17 00:00:00 2001 From: Dominique Hazael-Massieux Date: Thu, 22 Mar 2018 16:08:05 +0100 Subject: [PATCH 1/4] Improve accessibility of table markup used for tooltips --- src/scss/tooltip.scss | 6 ++++-- src/tooltip.js | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/scss/tooltip.scss b/src/scss/tooltip.scss index 99f36f9a5..116942575 100644 --- a/src/scss/tooltip.scss +++ b/src/scss/tooltip.scss @@ -21,13 +21,15 @@ text-align:left; color:#FFF; } -.c3-tooltip td { +.c3-tooltip td, .c3-tooltip th[scope='row'] { font-size:13px; padding: 3px 6px; background-color:#fff; border-left:1px dotted #999; + color: black; + font-weight: normal; } -.c3-tooltip td > span { +.c3-tooltip td > span, , .c3-tooltip th[scope='row'] > span { display: inline-block; width: 10px; height: 10px; diff --git a/src/tooltip.js b/src/tooltip.js index 3e8108cc4..58cb608b0 100644 --- a/src/tooltip.js +++ b/src/tooltip.js @@ -114,7 +114,7 @@ c3_chart_internal_fn.getTooltipContent = function (d, defaultTitleFormat, defaul if (! text) { title = sanitise(titleFormat ? titleFormat(d[i].x) : d[i].x); - text = "" + (title || title === 0 ? "" : ""); + text = "
" + title + "
" + (title || title === 0 ? "" : ""); } value = sanitise(valueFormat(d[i].value, d[i].ratio, d[i].id, d[i].index, d)); @@ -125,7 +125,7 @@ c3_chart_internal_fn.getTooltipContent = function (d, defaultTitleFormat, defaul bgcolor = $$.levelColor ? $$.levelColor(d[i].value) : color(d[i].id); text += ""; - text += ""; + text += ""; text += ""; text += ""; } From fa78a55c24602591a498de55436f3bdd56d17efa Mon Sep 17 00:00:00 2001 From: Dominique Hazael-Massieux Date: Thu, 22 Mar 2018 17:42:37 +0100 Subject: [PATCH 2/4] Add aria annotation for tooltip / x-grid focus --- src/core.js | 5 ++++- src/grid.js | 1 + src/interaction.js | 1 + src/tooltip.js | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core.js b/src/core.js index 52b6d92ac..87075535d 100644 --- a/src/core.js +++ b/src/core.js @@ -86,7 +86,8 @@ c3_chart_internal_fn.initParams = function () { var $$ = this, d3 = $$.d3, config = $$.config; // MEMO: clipId needs to be unique because it conflicts when multiple charts exist - $$.clipId = "c3-" + (+new Date()) + '-clip', + $$.chartId = "c3-" + (+new Date()), + $$.clipId = $$.chartId + '-clip', $$.clipIdForXAxis = $$.clipId + '-xaxis', $$.clipIdForYAxis = $$.clipId + '-yaxis', $$.clipIdForGrid = $$.clipId + '-grid', @@ -96,6 +97,8 @@ c3_chart_internal_fn.initParams = function () { $$.clipPathForYAxis = $$.getClipPath($$.clipIdForYAxis); $$.clipPathForGrid = $$.getClipPath($$.clipIdForGrid), $$.clipPathForSubchart = $$.getClipPath($$.clipIdForSubchart), + $$.tooltipId = $$.chartId + '-tooltip', + $$.xgridFocusId = $$.chartId + '-xgrid', $$.dragStart = null; $$.dragging = false; diff --git a/src/grid.js b/src/grid.js index ddf417eee..e9d17687f 100644 --- a/src/grid.js +++ b/src/grid.js @@ -16,6 +16,7 @@ c3_chart_internal_fn.initGrid = function () { if (config.grid_focus_show) { $$.grid.append('g') .attr("class", CLASS.xgridFocus) + .attr("id", $$.xgridFocusId) .append('line') .attr('class', CLASS.xgridFocus); } diff --git a/src/interaction.js b/src/interaction.js index a51f6de50..d20e5f63c 100644 --- a/src/interaction.js +++ b/src/interaction.js @@ -5,6 +5,7 @@ c3_chart_internal_fn.initEventRect = function () { var $$ = this; $$.main.select('.' + CLASS.chart).append("g") .attr("class", CLASS.eventRects) + .attr('aria-controls', $$.tooltipId + ' ' + $$.xgridFocusId) .style('fill-opacity', 0); }; c3_chart_internal_fn.redrawEventRect = function () { diff --git a/src/tooltip.js b/src/tooltip.js index 58cb608b0..6f6dc5cf2 100644 --- a/src/tooltip.js +++ b/src/tooltip.js @@ -114,7 +114,7 @@ c3_chart_internal_fn.getTooltipContent = function (d, defaultTitleFormat, defaul if (! text) { title = sanitise(titleFormat ? titleFormat(d[i].x) : d[i].x); - text = "
" + title + "
" + name + "" + name + "" + value + "
" + (title || title === 0 ? "" : ""); + text = "
" + title + "
" + (title || title === 0 ? "" : ""); } value = sanitise(valueFormat(d[i].value, d[i].ratio, d[i].id, d[i].index, d)); From ca6dfbe0ab0d575103fe75328069ffba35de4656 Mon Sep 17 00:00:00 2001 From: Dominique Hazael-Massieux Date: Thu, 22 Mar 2018 18:27:36 +0100 Subject: [PATCH 3/4] Make chart keyboard navigable Encompass event rect in a elements (to make them focusable) Add focus/blur events as trigger for equivalent mouse events --- src/interaction.js | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/interaction.js b/src/interaction.js index d20e5f63c..8f3b59fae 100644 --- a/src/interaction.js +++ b/src/interaction.js @@ -107,6 +107,7 @@ c3_chart_internal_fn.updateEventRect = function (eventRectUpdate) { eventRectUpdate .attr('class', $$.classEvent.bind($$)) + .select('rect') .attr("x", x) .attr("y", y) .attr("width", w) @@ -114,8 +115,10 @@ c3_chart_internal_fn.updateEventRect = function (eventRectUpdate) { }; c3_chart_internal_fn.generateEventRectsForSingleX = function (eventRectEnter) { var $$ = this, d3 = $$.d3, config = $$.config; - eventRectEnter.append("rect") + eventRectEnter.append("a") .attr("class", $$.classEvent.bind($$)) + .attr("xlink:href", '') + .attr("aria-describedby", $$.tooltipId) .style("cursor", config.data_selection_enabled && config.data_selection_grouped ? "pointer" : null) .on('mouseover', function (d) { var index = d.index; @@ -132,6 +135,13 @@ c3_chart_internal_fn.generateEventRectsForSingleX = function (eventRectEnter) { config.data_onmouseover.call($$.api, d); }); }) + .on('focus', function(d) { + $$.dispatchEvent('mouseover', d.index); + $$.dispatchEvent('mousemove', d.index); + }) + .on('blur', function(d) { + $$.dispatchEvent('mouseout', d.index); + }) .on('mouseout', function (d) { var index = d.index; if (!$$.config) { return; } // chart is destroyed @@ -202,6 +212,7 @@ c3_chart_internal_fn.generateEventRectsForSingleX = function (eventRectEnter) { }); }) .on('click', function (d) { + d3.event.preventDefault(); var index = d.index; if ($$.hasArcType() || !$$.toggleShape) { return; } if ($$.cancelClick) { @@ -225,7 +236,8 @@ c3_chart_internal_fn.generateEventRectsForSingleX = function (eventRectEnter) { .on('dragstart', function () { $$.dragstart(d3.mouse(this)); }) .on('dragend', function () { $$.dragend(); }) ) : function () {} - ); + ) + .append('rect'); }; c3_chart_internal_fn.generateEventRectsForMultipleXs = function (eventRectEnter) { @@ -239,12 +251,10 @@ c3_chart_internal_fn.generateEventRectsForMultipleXs = function (eventRectEnter) $$.unexpandBars(); } - eventRectEnter.append('rect') - .attr('x', 0) - .attr('y', 0) - .attr('width', $$.width) - .attr('height', $$.height) + eventRectEnter.append('a') .attr('class', CLASS.eventRect) + .attr('xlink:href', '') + .attr("aria-describedby", $$.tooltipId) .on('mouseout', function () { if (!$$.config) { return; } // chart is destroyed if ($$.hasArcType()) { return; } @@ -300,7 +310,14 @@ c3_chart_internal_fn.generateEventRectsForMultipleXs = function (eventRectEnter) } } }) + .on('focus', function() { + $$.dispatchEvent('mousemove'); + }) + .on('blur', function() { + $$.dispatchEvent('mouseout'); + }) .on('click', function () { + d3.event.preventDefault(); var targetsToShow = $$.filterTargetsToShow($$.data.targets); var mouse, closest; if ($$.hasArcType(targetsToShow)) { return; } @@ -325,7 +342,11 @@ c3_chart_internal_fn.generateEventRectsForMultipleXs = function (eventRectEnter) .on('dragstart', function () { $$.dragstart(d3.mouse(this)); }) .on('dragend', function () { $$.dragend(); }) ) : function () {} - ); + ).append('rect') + .attr('x', 0) + .attr('y', 0) + .attr('width', $$.width) + .attr('height', $$.height); }; c3_chart_internal_fn.dispatchEvent = function (type, index, mouse) { var $$ = this, From b21c0cf4249b1b460fbf1479b427726aaf968c50 Mon Sep 17 00:00:00 2001 From: Dominique Hazael-Massieux Date: Thu, 22 Mar 2018 18:40:16 +0100 Subject: [PATCH 4/4] Add a couple more ARIA annotations --- src/core.js | 1 + src/shape.line.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/core.js b/src/core.js index 87075535d..d89f5d253 100644 --- a/src/core.js +++ b/src/core.js @@ -231,6 +231,7 @@ c3_chart_internal_fn.initWithData = function (data) { // Define svgs $$.svg = $$.selectChart.append("svg") + .attr('role', 'graphics-document document') .style("overflow", "hidden") .on('mouseenter', function () { return config.onmouseover.call($$); }) .on('mouseleave', function () { return config.onmouseout.call($$); }); diff --git a/src/shape.line.js b/src/shape.line.js index bdb03c714..9f85dab78 100644 --- a/src/shape.line.js +++ b/src/shape.line.js @@ -20,6 +20,7 @@ c3_chart_internal_fn.updateTargetsForLine = function (targets) { .attr('class', function (d) { return classChartLine(d) + classFocus(d); }); mainLineEnter = mainLineUpdate.enter().append('g') .attr('class', classChartLine) + .attr('role', 'graphics-object group') .style('opacity', 0) .style("pointer-events", "none"); // Lines for each data @@ -309,6 +310,7 @@ c3_chart_internal_fn.updateCircle = function () { $$.mainCircle = $$.main.selectAll('.' + CLASS.circles).selectAll('.' + CLASS.circle) .data($$.lineOrScatterData.bind($$)); $$.mainCircle.enter().append("circle") + .attr("role", "graphics-symbol img") .attr("class", $$.classCircle.bind($$)) .attr("r", $$.pointR.bind($$)) .style("fill", $$.color);