Skip to content

Commit

Permalink
Keyboard navigation + aria labels (#123)
Browse files Browse the repository at this point in the history
* Add keyboard navigation

* Increment visual version

* Add aria-labels

* Add tests for keyboard navigation

* Fix npm audit

---------

Co-authored-by: Iuliia Kulagina <v-ikulagina@microsoft.com>
  • Loading branch information
kullJul and kullJul authored Jun 17, 2024
1 parent 3e94316 commit f8fa7db
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 49 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 2.3.1.0
* Added keyboard navigation

## 2.3.0.0
* API v5.9.0
* Use selectionManager for interactive behavior
Expand Down
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "powerbi-visuals-wordcloud",
"version": "2.3.0.0",
"version": "2.3.1.0",
"description": "Word Cloud is a visual representation of word frequency and value. Use it to get instant insight into the most important terms in a set.",
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions pbiviz.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"visual": {
"name": "WordCloud",
"displayName": "WordCloud 2.3.0.0",
"displayName": "WordCloud 2.3.1.0",
"guid": "WordCloud1447959067750",
"visualClassName": "WordCloud",
"version": "2.3.0.0",
"version": "2.3.1.0",
"description": "Word Cloud is a visual representation of word frequency and value. Use it to get instant insight into the most important terms in a set.",
"supportUrl": "https://community.powerbi.com",
"gitHubUrl": "https://github.com/Microsoft/PowerBI-visuals-WordCloud"
Expand Down
14 changes: 12 additions & 2 deletions src/WordCloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import PrimitiveValue = powerbiVisualsApi.PrimitiveValue;
import DataViewCategoryColumn = powerbiVisualsApi.DataViewCategoryColumn;
import DataViewObjectPropertyIdentifier = powerbiVisualsApi.DataViewObjectPropertyIdentifier;

import IColorPalette = powerbiVisualsApi.extensibility.IColorPalette;
import IColorPalette = powerbiVisualsApi.extensibility.ISandboxExtendedColorPalette;
import IVisualEventService = powerbiVisualsApi.extensibility.IVisualEventService;
import ISelectionManager = powerbiVisualsApi.extensibility.ISelectionManager;

Expand Down Expand Up @@ -350,6 +350,7 @@ export class WordCloud implements IVisual {
private static YOffsetPosition: number = 0.75;
private static HeightOffsetPosition: number = 0.85;
private static TextFillColor: string = "rgba(63, 191, 191, 0.0)";
private static WordOutlineColor: string = "black";

private static MinFontSize: number = 0;
private static DefaultAngle: number = 0;
Expand Down Expand Up @@ -817,7 +818,10 @@ export class WordCloud implements IVisual {
this.fontFamily = this.root.style("font-family");

this.main = this.root.append("g");
this.main.append("g").classed(WordCloud.Words.className, true);
this.main.append("g")
.classed(WordCloud.Words.className, true)
.attr("role", "listbox")
.attr("aria-multiselectable", "true");

// init canvas context for calculate label positions
const canvas = document.createElement("canvas");
Expand Down Expand Up @@ -1441,6 +1445,12 @@ export class WordCloud implements IVisual {
.attr("y", (dataPoint: WordCloudDataPoint) => -dataPoint.size * WordCloud.YOffsetPosition)
.attr("height", (dataPoint: WordCloudDataPoint) => dataPoint.size * WordCloud.HeightOffsetPosition)
.attr("fill", () => WordCloud.TextFillColor)
.attr("tabindex", 0)
.attr("stroke", this.colorPalette.isHighContrast ? this.colorPalette.foreground.value : WordCloud.WordOutlineColor)
.attr("role", "option")
.attr("aria-label", (dataPoint: WordCloudDataPoint) => {
return dataPoint.text;
})
.exit()
.remove();

Expand Down
79 changes: 51 additions & 28 deletions src/behavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,40 +53,15 @@ export class WordCloudBehavior {

this.bindClickEventToWords();
this.bindClickEventToClearCatcher();
this.bindKeyboardEventToWords();
this.renderSelection();
}

private bindClickEventToWords(): void {
this.behaviorOptions.wordsSelection.on("click", (event: PointerEvent, word: WordCloudDataPoint) => {
const isMultiSelection: boolean = event.ctrlKey || event.shiftKey || event.metaKey;
const wordKey = word.text.toLocaleLowerCase();
if (isMultiSelection){
if (!this.selectedWords.has(wordKey)){
this.selectedWords.add(wordKey);
this.selectionManager.select(word.selectionIds, true);
}
else {
this.selectedWords.delete(wordKey);
const idsToSelect: ISelectionId[] = this.getSelectionIds(Array.from(this.selectedWords));
idsToSelect.length === 0
? this.selectionManager.clear()
: this.selectionManager.select(idsToSelect);
}
}
else {
if (this.selectedWords.has(wordKey) && this.selectedWords.size === 1){
this.selectedWords.clear();
this.selectionManager.clear();
}
else {
this.selectedWords.clear();
this.selectedWords.add(wordKey);
this.selectionManager.select(word.selectionIds);
}
}

this.renderSelection();
this.selectWord(event, word);
event.stopPropagation();
this.renderSelection();
});

this.behaviorOptions.wordsSelection.on("contextmenu", (event: PointerEvent, word: WordCloudDataPoint) => {
Expand All @@ -103,6 +78,35 @@ export class WordCloudBehavior {
});
}

private selectWord(event: PointerEvent | KeyboardEvent, word: WordCloudDataPoint):void{
const isMultiSelection: boolean = event.ctrlKey || event.shiftKey || event.metaKey;
const wordKey = word.text.toLocaleLowerCase();
if (isMultiSelection){
if (!this.selectedWords.has(wordKey)){
this.selectedWords.add(wordKey);
this.selectionManager.select(word.selectionIds, true);
}
else {
this.selectedWords.delete(wordKey);
const idsToSelect: ISelectionId[] = this.getSelectionIds(Array.from(this.selectedWords));
idsToSelect.length === 0
? this.selectionManager.clear()
: this.selectionManager.select(idsToSelect);
}
}
else {
if (this.selectedWords.has(wordKey) && this.selectedWords.size === 1){
this.selectedWords.clear();
this.selectionManager.clear();
}
else {
this.selectedWords.clear();
this.selectedWords.add(wordKey);
this.selectionManager.select(word.selectionIds);
}
}
}

private bindClickEventToClearCatcher(): void {
this.behaviorOptions.root.on("click", () => {
this.selectedWords.clear();
Expand All @@ -123,13 +127,24 @@ export class WordCloudBehavior {
});
}

private bindKeyboardEventToWords(): void {
this.behaviorOptions.wordsSelection.on("keydown", (event : KeyboardEvent, word: WordCloudDataPoint) => {
if(event?.code == "Enter" || event?.code == "Space") {
this.selectWord(event, word);
event.stopPropagation();
this.renderSelection();
}
});
}

private renderSelection(): void {
if (!this.behaviorOptions.wordsSelection) {
return;
}

if (!this.selectionManager.hasSelection()) {
this.setOpacity(this.behaviorOptions.wordsSelection, WordCloudBehavior.MaxOpacity);
this.setAriaSelectedLabel(this.behaviorOptions.wordsSelection);

return;
}
Expand All @@ -142,9 +157,17 @@ export class WordCloudBehavior {

this.setOpacity(this.behaviorOptions.wordsSelection, WordCloudBehavior.MinOpacity);
this.setOpacity(selectedColumns, WordCloudBehavior.MaxOpacity);
this.setAriaSelectedLabel(this.behaviorOptions.wordsSelection);
}

private setOpacity(element: Selection<any>, opacityValue: number): void {
element.style("fill-opacity", opacityValue);
}

private setAriaSelectedLabel(element: Selection<any>){
element.attr("aria-selected", (dataPoint: WordCloudDataPoint) => {
const wordKey: string = dataPoint.text.toLocaleLowerCase();
return this.selectedWords.has(wordKey);
});
}
}
10 changes: 10 additions & 0 deletions style/visual.less
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@

rect {
cursor: pointer;
stroke-opacity: 0;

&:focus{
outline: none;
}

&:focus-visible{
stroke-width: 3px;
stroke-opacity: 1;
}
}
}
}
4 changes: 4 additions & 0 deletions test/WordCloudBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export class WordCloudBuilder extends VisualBuilderBase<VisualClass> {
return this.element.querySelector("svg.wordCloud");
}

public get word(): SVGElement {
return this.mainElement?.querySelector("g > g.words");
}

public get words(): NodeListOf<SVGElement> {
return this.mainElement?.querySelectorAll("g > g.words > g.word");
}
Expand Down
Loading

0 comments on commit f8fa7db

Please sign in to comment.