admin管理员组文章数量:1026989
Any hints how to control d3 brush/zoom with keyboard: 1. Ability to focus on brush control 2. Ability to change brush area using keyboard
Is it supported out of the box?
Update: Apparently there is no out of the box solution (hope d3 will provide it at some point). It means that a custom solution will depend on visualization/scenario. Posting actual UX and requirements and will provide a solution for this particular case.
In order to meet accessibility requirements the task was to modify below chart control to be able to zoom/brush using keyboard. This includes: 1) being able to set focus; 2) being able to control using left and right arrow keys.
Any hints how to control d3 brush/zoom with keyboard: 1. Ability to focus on brush control 2. Ability to change brush area using keyboard
Is it supported out of the box?
Update: Apparently there is no out of the box solution (hope d3 will provide it at some point). It means that a custom solution will depend on visualization/scenario. Posting actual UX and requirements and will provide a solution for this particular case.
In order to meet accessibility requirements the task was to modify below chart control to be able to zoom/brush using keyboard. This includes: 1) being able to set focus; 2) being able to control using left and right arrow keys.
Share Improve this question edited Nov 22, 2017 at 5:34 ZakiMa asked Nov 14, 2017 at 22:09 ZakiMaZakiMa 6,3261 gold badge27 silver badges55 bronze badges2 Answers
Reset to default 4 +100I'm going to use this bl.ock as a reference. I believe it is the source of your image.
Zoom and Brush Function Comparison
We are interested in a couple things in this block, the code for zooming and the code for brushing:
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
Both functions:
- check to see if the main body of the function should be executed
- set a new x scale domain
- update the area and axis
The differences are important:
The brush function updates the scale using d3.zoomIdentity, it must do this as it needs to update the zoom function to reflect the current zoom scale and transform.
The zoom function manually sets the brush, it must do this because the brush needs to be updated.
Zoom In and Brush "In" Without Events
To control this by the keyboard, it is probably easier to use the brushed() function as a template. This is because the current zoom transform can be tricky to retrieve whereas it is relatively easy to spoof a change in the brush.
In the brushed function the value in d3.event.selection is an array containing the range of values contained by the brush (values in the range, not the domain). It is an array of the minimum and maximum range values in the reference/context scale, x2, covered by the brush. This is the only thing we need to update both zoom and brush.
To zoom in, we can take the focus x scale's domain and find the domain's minimum and maximum values. Then we can re-set the focus x scale's domain to be slightly smaller, zooming in effectively. The code below converts the domain to a range, and reduces that range before converting it back into a domain - this is unneeded but follows the brushed() function more closely and means not having to deal with dates.
var xMin = x2(x.domain()[0]);
var xMax = x2(x.domain()[1]);
var currentDifference = Math.abs(xMin-xMax);
xMin += currentDifference / 2 / 3 // increase the minimum value of the domain
xMax -= currentDifference / 2 / 3 // decrease the maximum value of the domain
x.domain([xMin,xMax].map(x2.invert, x2));
We can also set the zoom scale like so:
var identity = d3.zoomIdentity
.scale(width/ (xMax - xMin))
We also want to modify the zoom's transform so we are zooming in to the center of the previous larger domain. The following is just a reproduction of the code used in the example block, but with clearer names for the sake of illustration:
var identity = d3.zoomIdentity
.scale(width/ (xMax - xMin))
.translate(-xMin, 0);
If we use the brushed function as a template, we might end up with:
var xMin = x2(x.domain()[0]); // minimum value in x range currently
var xMax = x2(x.domain()[1]); // maximum value in x range currently
var currentDifference = Math.abs(xMax-xMin); // center point of range
xMin += currentDifference / 2 / 3 // reduce the distance between center point and end points
xMax -= currentDifference / 2 / 3
x.domain([xMin,xMax].map(x2.invert, x2)); // convert the range to a domain
focus.select(".area").attr("d", area); // redraw the chart
focus.select(".axis--x").call(xAxis); // redraw the axis
var identity = d3.zoomIdentity
.scale(width/ (xMax - xMin))
.translate(-xMin, 0); // update the zoom factor
context.select(".brush").call(brush.move, x.range().map(identity.invertX, identity)); // update the brush
svg.select(".zoom").call(zoom.transform, identity); // apply the zoom factor
This will zoom the focus area in to an area centered in the current domain's center. The domain will shrink by one third with the code above, but that can be changed to match your needs.
The only real differences pared to the original brushed function are that we are:
- manually puting the brush extent
- updating the brush with the method used in the zoomed function.
That is it.
Other Operations
You can zoom out by expanding rather than shrinking the domain, just switch the sign when defining the new end points:
xMin -= currentDifference / 2 / 3
xMax += currentDifference / 2 / 3
Moving left would look like:
xMin -= currentDifference / 2 / 3
xMax -= currentDifference / 2 / 3
And moving right naturally would be the opposite.
Adding the Keyboard
Now all you have to do is set up a listener to listen for key strikes:
d3.select("body")
.on("keypress", function() {
if (d3.event.key == "a") {
// one of zoom in/out/pan
}
else if (d3.event.key == "b" {
//...
}
});
Putting it All Together
I've assembled a block that shows it all together, I've used asdw for key inputs:
- a: pan left
- d: pan right
- w: zoom in
- s: zoom out.
One last note: I've included a check to make sure that the new domain is in bounds: we don't want to zoom beyond the domain of our data.
Here's the example.
Since SVG 2.0 is not here yet and in SVG 1.0 focusable elements are not supported I ended up using <a xlink:href="#">
trick to get a focus on left/right ticks. Also decided that getting a focus for the whole brush element is not necessarily because proper range can be achieved by moving left/right ticks.
private createResizeTick(resizeClass: string, id: string, brushTickClass: string, tickIndex: number, bottom: number) {
let self = this;
// +++++++++++++++++++ NEW CODE +++++++++++++++++++
let aElement = this._xBrushElement.selectAll(resizeClass)
.append('a')
.attr('id', id)
.attr('xlink:href', '#')
.on('keydown', () => {
if ((<KeyboardEvent>d3.event).keyCode !== 37 && (<KeyboardEvent>d3.event).keyCode !== 39) {
return;
}
// A function which adjusts brush's domain (specific to data model)
self.brushKeyMove((<KeyboardEvent>d3.event).keyCode, tickIndex);
})
.on('keyup', () => {
if ((<KeyboardEvent>d3.event).keyCode !== 37 && (<KeyboardEvent>d3.event).keyCode !== 39) {
return;
}
self.brushOnEnd(); // A function which already processes native onBrushEnd event
document.getElementById(id).focus();
});
// --------------- END OF NEW CODE ---------------
aElement.append('text')
.attr('class', 'brushtick ' + brushTickClass)
.attr('transform', 'translate(0,' + bottom + ')')
.attr('x', 0)
.attr('y', 6)
.attr('dy', '0.35em');
}
Here is the result:
Any hints how to control d3 brush/zoom with keyboard: 1. Ability to focus on brush control 2. Ability to change brush area using keyboard
Is it supported out of the box?
Update: Apparently there is no out of the box solution (hope d3 will provide it at some point). It means that a custom solution will depend on visualization/scenario. Posting actual UX and requirements and will provide a solution for this particular case.
In order to meet accessibility requirements the task was to modify below chart control to be able to zoom/brush using keyboard. This includes: 1) being able to set focus; 2) being able to control using left and right arrow keys.
Any hints how to control d3 brush/zoom with keyboard: 1. Ability to focus on brush control 2. Ability to change brush area using keyboard
Is it supported out of the box?
Update: Apparently there is no out of the box solution (hope d3 will provide it at some point). It means that a custom solution will depend on visualization/scenario. Posting actual UX and requirements and will provide a solution for this particular case.
In order to meet accessibility requirements the task was to modify below chart control to be able to zoom/brush using keyboard. This includes: 1) being able to set focus; 2) being able to control using left and right arrow keys.
Share Improve this question edited Nov 22, 2017 at 5:34 ZakiMa asked Nov 14, 2017 at 22:09 ZakiMaZakiMa 6,3261 gold badge27 silver badges55 bronze badges2 Answers
Reset to default 4 +100I'm going to use this bl.ock as a reference. I believe it is the source of your image.
Zoom and Brush Function Comparison
We are interested in a couple things in this block, the code for zooming and the code for brushing:
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.select(".area").attr("d", area);
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
Both functions:
- check to see if the main body of the function should be executed
- set a new x scale domain
- update the area and axis
The differences are important:
The brush function updates the scale using d3.zoomIdentity, it must do this as it needs to update the zoom function to reflect the current zoom scale and transform.
The zoom function manually sets the brush, it must do this because the brush needs to be updated.
Zoom In and Brush "In" Without Events
To control this by the keyboard, it is probably easier to use the brushed() function as a template. This is because the current zoom transform can be tricky to retrieve whereas it is relatively easy to spoof a change in the brush.
In the brushed function the value in d3.event.selection is an array containing the range of values contained by the brush (values in the range, not the domain). It is an array of the minimum and maximum range values in the reference/context scale, x2, covered by the brush. This is the only thing we need to update both zoom and brush.
To zoom in, we can take the focus x scale's domain and find the domain's minimum and maximum values. Then we can re-set the focus x scale's domain to be slightly smaller, zooming in effectively. The code below converts the domain to a range, and reduces that range before converting it back into a domain - this is unneeded but follows the brushed() function more closely and means not having to deal with dates.
var xMin = x2(x.domain()[0]);
var xMax = x2(x.domain()[1]);
var currentDifference = Math.abs(xMin-xMax);
xMin += currentDifference / 2 / 3 // increase the minimum value of the domain
xMax -= currentDifference / 2 / 3 // decrease the maximum value of the domain
x.domain([xMin,xMax].map(x2.invert, x2));
We can also set the zoom scale like so:
var identity = d3.zoomIdentity
.scale(width/ (xMax - xMin))
We also want to modify the zoom's transform so we are zooming in to the center of the previous larger domain. The following is just a reproduction of the code used in the example block, but with clearer names for the sake of illustration:
var identity = d3.zoomIdentity
.scale(width/ (xMax - xMin))
.translate(-xMin, 0);
If we use the brushed function as a template, we might end up with:
var xMin = x2(x.domain()[0]); // minimum value in x range currently
var xMax = x2(x.domain()[1]); // maximum value in x range currently
var currentDifference = Math.abs(xMax-xMin); // center point of range
xMin += currentDifference / 2 / 3 // reduce the distance between center point and end points
xMax -= currentDifference / 2 / 3
x.domain([xMin,xMax].map(x2.invert, x2)); // convert the range to a domain
focus.select(".area").attr("d", area); // redraw the chart
focus.select(".axis--x").call(xAxis); // redraw the axis
var identity = d3.zoomIdentity
.scale(width/ (xMax - xMin))
.translate(-xMin, 0); // update the zoom factor
context.select(".brush").call(brush.move, x.range().map(identity.invertX, identity)); // update the brush
svg.select(".zoom").call(zoom.transform, identity); // apply the zoom factor
This will zoom the focus area in to an area centered in the current domain's center. The domain will shrink by one third with the code above, but that can be changed to match your needs.
The only real differences pared to the original brushed function are that we are:
- manually puting the brush extent
- updating the brush with the method used in the zoomed function.
That is it.
Other Operations
You can zoom out by expanding rather than shrinking the domain, just switch the sign when defining the new end points:
xMin -= currentDifference / 2 / 3
xMax += currentDifference / 2 / 3
Moving left would look like:
xMin -= currentDifference / 2 / 3
xMax -= currentDifference / 2 / 3
And moving right naturally would be the opposite.
Adding the Keyboard
Now all you have to do is set up a listener to listen for key strikes:
d3.select("body")
.on("keypress", function() {
if (d3.event.key == "a") {
// one of zoom in/out/pan
}
else if (d3.event.key == "b" {
//...
}
});
Putting it All Together
I've assembled a block that shows it all together, I've used asdw for key inputs:
- a: pan left
- d: pan right
- w: zoom in
- s: zoom out.
One last note: I've included a check to make sure that the new domain is in bounds: we don't want to zoom beyond the domain of our data.
Here's the example.
Since SVG 2.0 is not here yet and in SVG 1.0 focusable elements are not supported I ended up using <a xlink:href="#">
trick to get a focus on left/right ticks. Also decided that getting a focus for the whole brush element is not necessarily because proper range can be achieved by moving left/right ticks.
private createResizeTick(resizeClass: string, id: string, brushTickClass: string, tickIndex: number, bottom: number) {
let self = this;
// +++++++++++++++++++ NEW CODE +++++++++++++++++++
let aElement = this._xBrushElement.selectAll(resizeClass)
.append('a')
.attr('id', id)
.attr('xlink:href', '#')
.on('keydown', () => {
if ((<KeyboardEvent>d3.event).keyCode !== 37 && (<KeyboardEvent>d3.event).keyCode !== 39) {
return;
}
// A function which adjusts brush's domain (specific to data model)
self.brushKeyMove((<KeyboardEvent>d3.event).keyCode, tickIndex);
})
.on('keyup', () => {
if ((<KeyboardEvent>d3.event).keyCode !== 37 && (<KeyboardEvent>d3.event).keyCode !== 39) {
return;
}
self.brushOnEnd(); // A function which already processes native onBrushEnd event
document.getElementById(id).focus();
});
// --------------- END OF NEW CODE ---------------
aElement.append('text')
.attr('class', 'brushtick ' + brushTickClass)
.attr('transform', 'translate(0,' + bottom + ')')
.attr('x', 0)
.attr('y', 6)
.attr('dy', '0.35em');
}
Here is the result:
本文标签: javascriptAccessibility d3 brushzoom can get focus and be controlled with keyboardStack Overflow
版权声明:本文标题:javascript - Accessibility: d3 brushzoom can get focus and be controlled with keyboard - Stack Overflow 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/questions/1745659934a2161827.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论