-
Notifications
You must be signed in to change notification settings - Fork 64
feat: add keyboard navigation for select charts #4481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds keyboard navigation functionality to Column and Pie chart components, enabling users to navigate and interact with chart elements using the Tab key and Space/Enter keys for selection. The changes also update repository references from "trunk" to "main" branch across multiple package.json files.
Key changes:
- Adds keyboard navigation support with Tab, Space, and Enter keys for Column and Pie charts
- Implements ARIA attributes and focus states for improved accessibility
- Updates CSS styling for hover, focus, and selection states with visual feedback
- Updates homepage URLs from "trunk" to "main" branch in package.json files
Reviewed Changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/util/package.json | Updated homepage URL from trunk to main branch |
| packages/observablehq-compiler/package.json | Updated homepage URL from trunk to main branch |
| packages/markdown-it-plugins/package.json | Updated homepage URL from trunk to main branch (with typo) |
| packages/dataflow/package.json | Updated homepage URL from trunk to main branch |
| packages/chart/src/Pie.ts | Added keyboard navigation, ARIA attributes, and slice padding functionality to Pie chart |
| packages/chart/src/Pie.css | Added CSS for focus states, hover effects, and keyboard navigation styling |
| packages/chart/src/Column.ts | Added keyboard navigation, ARIA attributes, and Tabster integration to Column chart |
| packages/chart/src/Column.css | Updated CSS for selected state, added focus and hover styles |
| packages/chart/index.html | Added demo controls for testing keyboard navigation on both chart types |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| "url": "https://github.com/hpcc-systems/Visualization/issues" | ||
| }, | ||
| "homepage": "https://github.com/hpcc-systems/Visualization/tree/trunk/packages/markdonw-it-plugins" | ||
| "homepage": "https://github.com/hpcc-systems/Visualization/tree/main/packages/markdonw-it-plugins" |
Copilot
AI
Oct 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected spelling of 'markdonw' to 'markdown' in the homepage URL path.
| "homepage": "https://github.com/hpcc-systems/Visualization/tree/main/packages/markdonw-it-plugins" | |
| "homepage": "https://github.com/hpcc-systems/Visualization/tree/main/packages/markdown-it-plugins" |
| this._totalValue = this.calcTotalValue(); | ||
| const innerRadius = this.calcInnerRadius(); | ||
| const outerRadius = this.calcOuterRadius(); | ||
| const innerRadius = Math.max(this.calcInnerRadius(), Math.min(outerRadius / 30, 6)); |
Copilot
AI
Oct 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic numbers 30 and 6 should be defined as named constants to clarify their purpose (e.g., MIN_INNER_RADIUS_RATIO = 30, MIN_INNER_RADIUS_PIXELS = 6).
| const paddingValue = this.slicePadding(); | ||
|
|
||
| // Use a small pad angle for visual separation | ||
| const padAngleRadians = paddingValue > 0 ? Math.min(paddingValue, 0.05) : 0; |
Copilot
AI
Oct 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic number 0.05 should be defined as a named constant (e.g., MAX_PAD_ANGLE = 0.05) to clarify the maximum allowed pad angle.
| .domain(context.layerColumns(host).filter(function (_d, idx) { return idx > 0; })) | ||
| .rangeRound(isHorizontal ? [0, dataLen] : [dataLen, 0]) | ||
| .paddingInner(this.xAxisSeriesPaddingInner()) | ||
| .paddingInner(Math.max(this.xAxisSeriesPaddingInner(), 0.05)) |
Copilot
AI
Oct 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic number 0.05 should be defined as a named constant (e.g., MIN_SERIES_PADDING = 0.05) to clarify the minimum padding value.
cd27015 to
f140d09
Compare
ac285a3 to
136469b
Compare
f140d09 to
0c9700a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 17 out of 18 changed files in this pull request and generated 24 comments.
Comments suppressed due to low confidence (2)
packages/common/src/TitleBar.ts:36
- The button element (anchor tag) should have
role="button"andtabindex="0"attributes for proper accessibility. While anchor tags are naturally focusable, explicitly setting these attributes ensures correct ARIA semantics when the element behaves as a button rather than a link.
this._i = this._element
.attr("href", "#")
.on("click", function () {
context.click();
d3Event.preventDefault();
})
.on("keydown", function (this: HTMLElement) {
if (d3Event.key === " " || d3Event.key === "Spacebar" || d3Event.code === "Space") {
this.click();
d3Event.preventDefault();
}
})
.on("mousemove", this.mouseMove)
.on("mouseout", this.mouseOut)
.append("i")
;
packages/graph/tests/test6.ts:75
- Unused variable g.
const g = genData2();
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
|
|
||
| constructor() { | ||
| super(); | ||
| const g = genData2(); |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This variable g is assigned but never used. It appears to be dead code that should be removed.
| const g = genData2(); |
| .common_IconBar { | ||
| display: flex; | ||
| flex-flow: row; | ||
| justify-content: space-between; | ||
| } | ||
|
|
||
| .common_IconBar .icon-bar { | ||
| display: flex; | ||
| opacity: 0.95; | ||
| flex-wrap: nowrap; | ||
| margin: 0px; | ||
| white-space: nowrap; | ||
| line-height: 28px; | ||
| z-index: 1; | ||
| } | ||
|
|
||
| .common_IconBar .icon-bar a { | ||
| position: relative; | ||
| z-index: 0; | ||
| text-align: center; | ||
| padding-top: 4px; | ||
| padding-left: 2px; | ||
| padding-right: 2px; | ||
| padding-bottom: 4px; | ||
| transition: all 0.3s ease; | ||
| color: darkgray; | ||
| } | ||
|
|
||
| .common_IconBar .icon-bar a:focus, | ||
| .common_IconBar .icon-bar a:focus-visible { | ||
| outline: 2px solid #0078d4; | ||
| outline-offset: 2px; | ||
| z-index: 2; | ||
| } | ||
|
|
||
| .common_IconBar .icon-bar a.disabled { | ||
| opacity: 0.3; | ||
| pointer-events: none; | ||
| color: darkgray; | ||
| } | ||
|
|
||
| .common_IconBar .icon-bar a:hover { | ||
| background-color: whitesmoke; | ||
| } | ||
|
|
||
| .common_IconBar .icon-bar a { | ||
| background-color: white; | ||
| } | ||
|
|
||
| .common_IconBar .icon-bar a.selected { | ||
| background-color: #efe5e5; | ||
| } | ||
|
|
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This CSS block is completely duplicated from lines 1-56. The entire section from .common_IconBar through .common_IconBar .icon-bar a.selected is repeated. This duplication should be removed to improve maintainability and reduce file size.
| .common_IconBar { | |
| display: flex; | |
| flex-flow: row; | |
| justify-content: space-between; | |
| } | |
| .common_IconBar .icon-bar { | |
| display: flex; | |
| opacity: 0.95; | |
| flex-wrap: nowrap; | |
| margin: 0px; | |
| white-space: nowrap; | |
| line-height: 28px; | |
| z-index: 1; | |
| } | |
| .common_IconBar .icon-bar a { | |
| position: relative; | |
| z-index: 0; | |
| text-align: center; | |
| padding-top: 4px; | |
| padding-left: 2px; | |
| padding-right: 2px; | |
| padding-bottom: 4px; | |
| transition: all 0.3s ease; | |
| color: darkgray; | |
| } | |
| .common_IconBar .icon-bar a:focus, | |
| .common_IconBar .icon-bar a:focus-visible { | |
| outline: 2px solid #0078d4; | |
| outline-offset: 2px; | |
| z-index: 2; | |
| } | |
| .common_IconBar .icon-bar a.disabled { | |
| opacity: 0.3; | |
| pointer-events: none; | |
| color: darkgray; | |
| } | |
| .common_IconBar .icon-bar a:hover { | |
| background-color: whitesmoke; | |
| } | |
| .common_IconBar .icon-bar a { | |
| background-color: white; | |
| } | |
| .common_IconBar .icon-bar a.selected { | |
| background-color: #efe5e5; | |
| } |
| import { format as d3Format } from "d3-format"; | ||
| import { scaleBand as d3ScaleBand } from "d3-scale"; | ||
| import { local as d3Local, select as d3Select } from "d3-selection"; | ||
| // import { createTabster, getGroupper, getTabster, type Types as TabsterTypes } from "tabster"; |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This commented import for tabster should be removed if it's not being used. Leaving commented-out imports reduces code clarity.
| // import { createTabster, getGroupper, getTabster, type Types as TabsterTypes } from "tabster"; |
| private textLocal = d3Local<Text>(); | ||
| private stackedTextLocal = d3Local<Text>(); | ||
| private isHorizontal: boolean; | ||
| // protected _tabster: TabsterTypes.TabsterCore | null = null; |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This commented-out protected property for tabster should be removed if it's not being used. Leaving commented-out code reduces maintainability.
| // protected _tabster: TabsterTypes.TabsterCore | null = null; |
| columnGEnter.merge(columnGRect as any) | ||
| .attr("tabindex", context.tabNavigation() ? 0 : undefined) // Tabster Groupper manages these inner focusables | ||
| .attr("role", context.tabNavigation() ? "button" : undefined) // ARIA role for accessibility | ||
| .attr("aria-label", context.tabNavigation() ? (d: any) => `${d.column}: ${d.value instanceof Array ? d.value[1] - d.value[0] : d.value}` : undefined) // ARIA label for screen readers |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The aria-label for column data should include the category/label from the domain (d.origRow[0]) to provide better context. For example: ${d.origRow[0]} - ${d.column}: ${...} would be more descriptive than just the column name and value.
| .attr("aria-label", context.tabNavigation() ? (d: any) => `${d.column}: ${d.value instanceof Array ? d.value[1] - d.value[0] : d.value}` : undefined) // ARIA label for screen readers | |
| .attr("aria-label", context.tabNavigation() ? (d: any) => `${d.origRow[0]} - ${d.column}: ${d.value instanceof Array ? d.value[1] - d.value[0] : d.value}` : undefined) // ARIA label for screen readers |
|
|
||
| this.d3Pie | ||
| .padAngle(0.0025) | ||
| .padAngle(0) // No geometric padding - CSS-only solution |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line sets padAngle to 0, but in the update method (lines 206-209, 215), the padAngle is calculated from slicePadding() and applied to d3Arc. There's a conflict: d3Pie.padAngle(0) here will be overridden by the calculated padAngle in the arc generator. Either remove this line or reconcile the padding logic between the pie generator and arc generator. The comment "No geometric padding - CSS-only solution" appears outdated given the new slicePadding implementation.
|
|
||
| <script type="module"> | ||
| import { Area, Column } from "@hpcc-js/chart"; | ||
| import { Area, Bar, Column, Pie } from "./src/index.ts"; |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused imports Area, Bar, Pie.
| import { Area, Bar, Column, Pie } from "./src/index.ts"; | |
| import { Column } from "./src/index.ts"; |
| </script> | ||
|
|
||
| <script type="module"> | ||
| import { Area, Column, Pie } from "./src/index.ts"; |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused imports Area, Column.
| import { Area, Column, Pie } from "./src/index.ts"; | |
| import { Pie } from "./src/index.ts"; |
| @@ -1,5 +1,5 @@ | |||
| import { INDChart, ITooltip } from "@hpcc-js/api"; | |||
| import { InputField } from "@hpcc-js/common"; | |||
| import { d3Event, InputField, SVGWidget } from "@hpcc-js/common"; | |||
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import SVGWidget.
| import { d3Event, InputField, SVGWidget } from "@hpcc-js/common"; | |
| import { d3Event, InputField } from "@hpcc-js/common"; |
| .text("Hello World") | ||
| .render() | ||
| ; | ||
| const iconBar = new IconBar() |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable iconBar.
| const iconBar = new IconBar() | |
| new IconBar() |
Signed-off-by: Gordon Smith <GordonJSmith@gmail.com>
0c9700a to
cb35c53
Compare
Checklist:
Testing: