10 | );
11 | }
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/examples/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import "./assets/style/index.css";
4 | import App from "./App";
5 |
6 | const root = ReactDOM.createRoot(document.getElementById("root"));
7 | root.render(
8 | <>
9 |
10 | >
11 | );
12 |
--------------------------------------------------------------------------------
/examples/src/data/transformJSON.js:
--------------------------------------------------------------------------------
1 | export default function transformJSON(inputJSON) {
2 | const outputArray = [];
3 |
4 | for (const key in inputJSON) {
5 | if (inputJSON.hasOwnProperty(key)) {
6 | outputArray.push({
7 | x: key,
8 | y: inputJSON[key],
9 | });
10 | }
11 | }
12 |
13 | return outputArray;
14 | }
15 |
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "cypress";
2 | import codeCoverageTask from "@cypress/code-coverage/task.js";
3 |
4 | export default defineConfig({
5 | e2e: {
6 | baseUrl: "http://localhost:5173/AutoVizuA11y/",
7 | experimentalRunAllSpecs: true,
8 | setupNodeEvents(on, config) {
9 | codeCoverageTask(on, config);
10 | return config;
11 | },
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/examples/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | const COMMON_SERVER_PORT = 5173;
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | server: {
9 | port: COMMON_SERVER_PORT,
10 | },
11 | preview: {
12 | port: COMMON_SERVER_PORT,
13 | },
14 | plugins: [react()],
15 | base: "/AutoVizuA11y/",
16 | });
17 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | AutoVizuA11y — examples
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/src/data/transformJSONmulti.js:
--------------------------------------------------------------------------------
1 | export default function transformJSONmulti(multiLineData) {
2 | const multiData = [];
3 |
4 | for (const country in multiLineData) {
5 | for (const year in multiLineData[country]) {
6 | multiData.push({
7 | x: parseInt(year),
8 | y: multiLineData[country][year],
9 | series: country,
10 | });
11 | }
12 | }
13 |
14 | return multiData;
15 | }
16 |
--------------------------------------------------------------------------------
/examples/src/assets/style/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 | .npmrc
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | package-lock.json
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | # Cypress files
27 | cypress/results
28 | cypress/screenshots
29 | cypress/downloads
30 |
31 | # Test coverage
32 | .nyc_output
33 | coverage
34 |
--------------------------------------------------------------------------------
/cypress/e2e/average.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Alt + K) Average Test", () => {
2 | it("should alert the correct average value for the first chart", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc").first().focus().type("{alt}k");
8 | cy.findAllByTestId("a11y-chart-alert").first().should("have.text", "The average is 456.99"); // Checks if the average is alerted and correct
9 | cy.checkA11y();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Testing
2 | on:
3 | - push
4 | - pull_request
5 | jobs:
6 | quality:
7 | name: E2E tests
8 | runs-on: ${{ matrix.os }}
9 | strategy:
10 | matrix:
11 | node-version:
12 | - 18.x
13 | - 20.x
14 | os:
15 | - ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Using node ${{ matrix.node-version }}
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: ${{ matrix.node-version }}
22 | cache: npm
23 | - run: npm ci
24 | - run: npm run test:ci
25 |
--------------------------------------------------------------------------------
/cypress/e2e/chart-attributes.cy.js:
--------------------------------------------------------------------------------
1 | describe("Addition of Attributes to Chart Test", () => {
2 | it("should add aria-labels and tabindexes to the charts", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc")
8 | .first()
9 | .as("chartDescription")
10 | .should("have.attr", "aria-label"); // Check if aria-label exists
11 | cy.get("@chartDescription").should("have.attr", "tabindex", "0"); // Check if tabindex="0" exists
12 | cy.checkA11y();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/examples/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react/jsx-no-target-blank': 'off',
16 | 'react-refresh/only-export-components': [
17 | 'warn',
18 | { allowConstantExport: true },
19 | ],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/cypress/e2e/data-level.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Down Arrow) Moving to Data Level Test", () => {
2 | it("should add back the tabindexes to the chart elements when moving from the chart level", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc")
8 | .first()
9 | .focus()
10 | .type("{downArrow}")
11 | .should("not.have.attr", "tabindex", "0"); // Checks if tabindex="0" does not exist
12 | cy.findAllByTestId("a11y-chart-element").first().should("have.attr", "tabindex", "0"); // Checks if tabindex="0" exists
13 | cy.checkA11y();
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/cypress/e2e/compare-to-maximum.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Alt + Shift + L) Compare to Maximum of the Chart Test", () => {
2 | it("should alert the correct comparison value between the first element and maximum of the chart", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc").first().focus().type("{downArrow}");
8 | cy.findAllByTestId("a11y-chart-element").first().type("{alt}{shift}l");
9 | cy.findAllByTestId("a11y-chart-alert")
10 | .first()
11 | .should("have.text", "The value is the same as the Maximum value");
12 | cy.checkA11y();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe your proposed solution**
13 | A clear and concise description of the feature you are proposing and what it should do.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/cypress/e2e/open-native-guide.cy.js:
--------------------------------------------------------------------------------
1 | describe("(?) Open Native Shortcut Guide Test", () => {
2 | it("should open the native shortcut guide", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y-native-shortcut-guide")
8 | .first()
9 | .as("nativeGuide")
10 | .should("not.be.visible"); // Check if the native shortcut guide is not visible (closed)
11 | cy.findAllByTestId("a11y_desc").first().focus().type("?");
12 | cy.get("@nativeGuide").should("be.visible"); // Check if the native shortcut guide is visible (opened)
13 | cy.checkA11y();
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/cypress/e2e/open-custom-guide.cy.js:
--------------------------------------------------------------------------------
1 | describe("(?) Open Custom Shortcut Guide Test", () => {
2 | it("should open the custom shortcut guide", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y-custom-shortcut-guide")
8 | .first()
9 | .as("customGuide")
10 | .should("not.be.visible"); // Check if the custom shortcut guide is not visible (closed)
11 | cy.findAllByTestId("a11y_desc").first().focus().tab().type("?");
12 | cy.get("@customGuide").should("be.visible"); // Check if the custom shortcut guide is visible (opened)
13 | cy.checkA11y();
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/cypress/e2e/close-custom-guide.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Esc) Close Custom Shortcut Guide Test", () => {
2 | it("should close the custom shortcut guide", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc").first().focus().tab().type("?");
8 | cy.get(":focus").click(); // Closing the guide by pressing the button
9 | cy.findAllByTestId("a11y-custom-shortcut-guide").first().should("not.be.visible"); // Check if the custom shortcut guide is not visible (closed)
10 | cy.findAllByTestId("a11y_desc").eq(1).should("be.focused"); // Checks if the chart is focused
11 | cy.checkA11y();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | ///
9 |
--------------------------------------------------------------------------------
/cypress/e2e/close-native-guide.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Esc) Close Native Shortcut Guide Test", () => {
2 | it("should close the native shortcut guide", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc").first().as("chartDescription").focus().type("?");
8 | cy.get(":focus").type("{esc}"); // Closing the guide by pressing the Esc
9 | cy.findAllByTestId("a11y-native-shortcut-guide").first().should("not.be.visible"); // Check if the native shortcut guide is not visible (closed)
10 | cy.get("@chartDescription").should("be.focused"); // Checks if the chart is focused
11 | cy.checkA11y();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/cypress/e2e/chart-level.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Up Arrow) Moving to Chart Level Test", () => {
2 | it("should add back the tabindexes to the charts when moving from the data level", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc").first().as("chartDescription").focus().type("{downArrow}");
8 | cy.findAllByTestId("a11y-chart-element")
9 | .first()
10 | .type("{upArrow}")
11 | .should("not.have.attr", "tabindex", "0"); // Checks if tabindex="0" does not exist
12 | cy.get("@chartDescription").should("have.attr", "tabindex", "0"); // Checks if tabindex="0" exists
13 | cy.checkA11y();
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/cypress/e2e/jump-between-chart-extremes.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Alt + W and Alt + Q) Jump to the Extremes of the Chart Test", () => {
2 | it("should set focus to the extreme elements of the chart", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc").first().focus().type("{downArrow}");
8 | cy.findAllByTestId("a11y-chart-element").first().as("firstChartElement").type("{alt}w");
9 | cy.findAllByTestId("a11y-chart-element").eq(9).should("be.focused").type("{alt}q"); // Checks if the last element is focused
10 | cy.get("@firstChartElement").should("be.focused"); // Checks if the first element is focused
11 | cy.checkA11y();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/cypress/e2e/move-between-chart-elements.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Right and Left Arrow) Move Between Elements of the Chart Test", () => {
2 | it("should move to the next and previous chart elements", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc").first().focus().type("{downArrow}");
8 | cy.findAllByTestId("a11y-chart-element").first().as("firstChartElement").type("{rightArrow}");
9 | cy.findAllByTestId("a11y-chart-element").eq(1).should("be.focused").type("{leftArrow}"); // Checks if the second element is focused
10 | cy.get("@firstChartElement").should("be.focused"); // Checks if the first element is focused
11 | cy.checkA11y();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/examples/src/assets/style/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | font-family: "Outfit";
4 | }
5 |
6 | .App-logo {
7 | height: 40vmin;
8 | pointer-events: none;
9 | }
10 |
11 | @media (prefers-reduced-motion: no-preference) {
12 | .App-logo {
13 | animation: App-logo-spin infinite 20s linear;
14 | }
15 | }
16 |
17 | .App-header {
18 | background-color: #282c34;
19 | min-height: 100vh;
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | justify-content: center;
24 | font-size: calc(10px + 2vmin);
25 | color: white;
26 | }
27 |
28 | .App-link {
29 | color: #61dafb;
30 | }
31 |
32 | @keyframes App-logo-spin {
33 | from {
34 | transform: rotate(0deg);
35 | }
36 | to {
37 | transform: rotate(360deg);
38 | }
39 | }
40 |
41 | .a11y-examples-title {
42 | font-size: 1rem;
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/descriptions/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | export * from "./DescriptionsKeyHandler";
9 | export * from "./DescriptionsGenerator";
10 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | export * from "./descriptions";
9 | export * from "./navigation";
10 | export * from "./insights";
11 |
--------------------------------------------------------------------------------
/cypress/support/e2e.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/e2e.ts is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import "./commands";
18 | import "cypress-axe";
19 | import "cypress-plugin-tab";
20 | import "@cypress/code-coverage/support";
21 |
22 | // Alternatively you can use CommonJS syntax:
23 | // require('./commands')
24 |
--------------------------------------------------------------------------------
/cypress/e2e/compare-to-chart.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Alt + Z) Compare to Rest of the Chart Test", () => {
2 | it("should alert the correct comparison value between the first element and rest of the chart", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc").first().as("chartDescription").focus().type("{alt}z");
8 | cy.findAllByTestId("a11y-chart-alert")
9 | .first()
10 | .as("chartAlert")
11 | .should("have.text", "This shortcut only works inside a chart");
12 | cy.get("@chartDescription").type("{downArrow}");
13 | cy.findAllByTestId("a11y-chart-element").first().type("{alt}z");
14 | cy.get("@chartAlert").should("have.text", "This is the highest value");
15 | cy.checkA11y();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/cypress/e2e/move-between-chart-series.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Alt + M) Move Between Data Series Test", () => {
2 | it("should move to the next series of data", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y_desc").first().focus().tab().tab().type("{downArrow}");
8 | cy.findAllByTestId("a11y-chart-element").eq(14).should("be.focused"); // Checks if the first element of the first series is focused
9 | cy.get(":focus").type("{alt}m");
10 | cy.findAllByTestId("a11y-chart-alert").first().should("have.text", "\u00A0"); // Checks if no error was alerted
11 | cy.findAllByTestId("a11y-chart-element").eq(18).should("be.focused"); // Checks if the first element of the second series is focused
12 | cy.checkA11y();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import "./i18n";
9 |
10 | export { AutoVizuA11y } from "./AutoVizuA11y";
11 | export { NativeShortcutGuide } from "./components/shortcut_guide/components/NativeShortcutGuide";
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Evidence**
24 | If applicable, add a screenshot, short video or gif to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 |
28 | - OS: [e.g. Windows, MacOS]
29 | - Browser: [e.g. Chrome, Safari]
30 | - Screen Reader: [e.g. VoiceOver, Jaws]
31 | - AutoVizuA11y Version: [e.g. 1.0.2]
32 |
33 | **Additional context**
34 | Add any other context about the problem here.
35 |
--------------------------------------------------------------------------------
/cypress/e2e/chart-element-attributes.cy.js:
--------------------------------------------------------------------------------
1 | describe("Addition of Attributes to Chart Elements Test", () => {
2 | it("should add aria-labels, aria-roledescription, empty role but not tabindexes to the chart elements", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.findAllByTestId("a11y-chart-element").first().as("chart-element");
8 | cy.get("@chart-element").should("have.attr", "aria-label"); // Check if aria-label exists
9 | cy.get("@chart-element").should("have.attr", "aria-roledescription"); // Check if aria-roledescription exists
10 | cy.get("@chart-element").should("have.attr", "role", "graphics-symbol"); // Check if role exists
11 | cy.get("@chart-element").should("not.have.attr", "tabindex", "0"); // Check if tabindex="0" does not exist
12 | cy.checkA11y();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/cypress/e2e/change-descriptions.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Alt + S and Alt + B) Change Chart Description Test", () => {
2 | it("should toggle between longer and shorter descriptions", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // Wait for AutoVizuA11y to handle descriptions
7 | cy.findAllByTestId("a11y_desc").first().as("chartDescription");
8 | cy.get("@chartDescription")
9 | .invoke("attr", "aria-label")
10 | .then((shortDescription) => {
11 | cy.get("@chartDescription")
12 | .focus()
13 | .type("{alt}b")
14 | .invoke("attr", "aria-label")
15 | .should("not.equal", shortDescription); // Assert that the aria-label has changed
16 | cy.get("@chartDescription")
17 | .focus()
18 | .type("{alt}s")
19 | .invoke("attr", "aria-label")
20 | .should("equal", shortDescription); // Assert that the aria-label reverted back
21 | });
22 | cy.checkA11y();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/components/navigation/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | export * from "./AddNavigation";
9 | export * from "./AddAriaLabels";
10 | export * from "./NavigationKeyHandler";
11 | export * from "./JumpX";
12 | export * from "./XSetter";
13 | export * from "./Skip";
14 | export * from "./GuideKeyHandler";
15 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | {
9 | "compilerOptions": {
10 | "composite": true,
11 | "skipLibCheck": true,
12 | "module": "ESNext",
13 | "moduleResolution": "bundler",
14 | "allowSyntheticDefaultImports": true,
15 | "strict": true
16 | },
17 | "include": ["vite.config.ts"]
18 | }
19 |
--------------------------------------------------------------------------------
/cypress/e2e/jump-between-chart-elements.cy.js:
--------------------------------------------------------------------------------
1 | describe("(Alt + X) Prompt Test", () => {
2 | it("should open a prompt when Alt + X is pressed and input '2'", () => {
3 | cy.visit("/");
4 | cy.injectAxe();
5 | cy.findByTestId("manual-descriptions-option").click();
6 | cy.wait(500); // AutoVizuA11y waits 500ms before handling descriptions
7 | cy.window().then((win) => {
8 | cy.stub(win, "prompt").returns("2").as("prompt"); // Stub the prompt after Alt + X is pressed
9 | cy.findAllByTestId("a11y_desc").first().focus().type("{downArrow}");
10 | cy.findAllByTestId("a11y-chart-element").first().as("firstChartElement").type("{alt}x"); // Simulate pressing Alt + X inside of the chart
11 | cy.get("@prompt").should("have.been.calledOnce"); // Verify the prompt was triggered and user input '2'
12 | cy.get("@firstChartElement").type("{rightArrow}");
13 | cy.findAllByTestId("a11y-chart-element").eq(2).should("be.focused"); // Checks if the third element is focused
14 | });
15 | cy.checkA11y();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | AutoVizuA11y
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/src/data/multipleEncodings.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "series": "Croatia",
4 | "x": 2010,
5 | "y": 4.37
6 | },
7 | {
8 | "series": "Croatia",
9 | "x": 2015,
10 | "y": 4.25
11 | },
12 | {
13 | "series": "Croatia",
14 | "x": 2020,
15 | "y": 4.1
16 | },
17 | {
18 | "series": "Croatia",
19 | "x": 2022,
20 | "y": 4.03
21 | },
22 | {
23 | "series": "Latvia",
24 | "x": 2010,
25 | "y": 2.1
26 | },
27 | {
28 | "series": "Latvia",
29 | "x": 2015,
30 | "y": 1.99
31 | },
32 | {
33 | "series": "Latvia",
34 | "x": 2020,
35 | "y": 1.9
36 | },
37 | {
38 | "series": "Latvia",
39 | "x": 2022,
40 | "y": 1.85
41 | },
42 | {
43 | "series": "Lithuania",
44 | "x": 2010,
45 | "y": 3.14
46 | },
47 | {
48 | "series": "Lithuania",
49 | "x": 2015,
50 | "y": 2.96
51 | },
52 | {
53 | "series": "Lithuania",
54 | "x": 2020,
55 |
56 | "y": 2.82
57 | },
58 | {
59 | "series": "Lithuania",
60 | "x": 2022,
61 | "y": 2.75
62 | }
63 | ]
64 |
--------------------------------------------------------------------------------
/src/utils/macOSDetector.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | /**
9 | * Detects if the user is on macOS
10 | * @returns {boolean} true if on macOS, false otherwise
11 | */
12 | export function isMacOS(): boolean {
13 | const userAgent = navigator.userAgent.toLowerCase();
14 | const platform = navigator.platform.toLowerCase();
15 |
16 | return platform.includes("mac") || userAgent.includes("mac os");
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/initToolTutorial.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import { getLSItem, setLSItem } from "@feedzai/js-utilities";
9 |
10 | /**
11 | * Ensures the tutorial is heard the first time a chart is focused
12 | *
13 | * @export
14 | */
15 | export function initToolTutorial() {
16 | const toolTutorial = getLSItem("toolTutorial");
17 |
18 | if (!toolTutorial) {
19 | setLSItem("toolTutorial", "true");
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | export * from "./arrayConverter";
9 | export * from "./insightsCalculator";
10 | export * from "./maths";
11 | export * from "./wiper";
12 | export * from "./handleBlur";
13 | export * from "./handleFirstFocus";
14 | export * from "./handleKeyDown";
15 | export * from "./showAlert";
16 | export * from "./initToolTutorial";
17 | export * from "./processData";
18 | export * from "./macOSDetector";
19 |
--------------------------------------------------------------------------------
/src/utils/handleBlur.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import React from "react";
9 |
10 | import * as constants from "./../constants";
11 |
12 | /**
13 | * Removes the 'focus' class when the element loses focus
14 | *
15 | * @export
16 | * @param {React.RefObject} chartRef - The React reference of the chart.
17 | */
18 | export const handleBlur = (chartRef: React.RefObject) => {
19 | chartRef.current!.classList.remove(constants.FOCUS_CLASS);
20 | };
21 |
--------------------------------------------------------------------------------
/src/components/shortcut_guide/components/NativeGuideDescription.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import * as constants from "../../../constants";
9 | import type { TFunction } from "i18next";
10 |
11 | export const ShortcutGuideDescription = ({ t }: { t: TFunction }) => {
12 | return (
13 |
17 | {t("sg_description")}
18 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/examples/src/components/custom_shortcut_guide/components/CustomShortcutGuideBody.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import * as constants from "../../../../../src/constants";
9 | import React from "react";
10 |
11 | import { CustomShortcutGuideSection } from "./CustomShortcutGuideSection";
12 | import { CUSTOM_GUIDE_DATA } from "./CustomGuideData";
13 |
14 | export const CustomShortcutGuideBody = () => (
15 |
22 | );
23 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 | import globals from "globals";
8 | import pluginJs from "@eslint/js";
9 | import tseslint from "typescript-eslint";
10 | import pluginReact from "eslint-plugin-react";
11 |
12 | const ESLINT_CONFIG = [
13 | pluginJs.configs.recommended,
14 | ...tseslint.configs.recommended,
15 | pluginReact.configs.flat?.recommended,
16 | {
17 | files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
18 | languageOptions: { globals: globals.browser },
19 | settings: {
20 | react: {
21 | version: "18"
22 | }
23 | },
24 | rules: {
25 | "react/react-in-jsx-scope": "off",
26 | "react/jsx-uses-react": "off",
27 | }
28 | }
29 | ];
30 |
31 | export default ESLINT_CONFIG;
32 |
--------------------------------------------------------------------------------
/src/utils/showAlert.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import { wait } from "@feedzai/js-utilities";
9 |
10 | import * as constants from "./../constants";
11 |
12 | /**
13 | * Sets a message in div with the 'alert' role and cleans it after some time
14 | *
15 | * @export
16 | * @param {React.ReactNode | null} alertDiv - Div where the alerts are set.
17 | * @param {string} message - The message to be set.
18 | */
19 | export async function showAlert(
20 | alertDivRef: React.RefObject,
21 | message: string,
22 | ): Promise {
23 | if (alertDivRef.current) {
24 | alertDivRef.current.textContent = message;
25 | await wait(constants.ALERT_DURATION);
26 | alertDivRef.current.textContent = "\u00A0";
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/shortcut_guide/components/NativeShortcutGuideBody.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import * as constants from "../../../constants";
9 | import { GUIDE_DATA } from "../../../assets/data/GuideData";
10 | import type { TFunction } from "i18next";
11 |
12 | import { ShortcutGuideSection } from "./NativeShortcutGuideSection";
13 |
14 | export const ShortcutGuideBody = ({ t }: { t: TFunction }) => {
15 | return (
16 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/utils/insightsCalculator.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import { sum, avg, max, min } from "./maths";
9 | /**
10 | * Calculates insights based on the array with only the values.
11 | *
12 | * @export
13 | * @param {number[]} arrayConverted - The array of numbers to calculate insights from.
14 | * @return {number[]} Array with the sum, average, maximum and minimum values.
15 | */
16 | export function insightsCalculator(arrayConverted: number[]): number[] {
17 | if (!arrayConverted || arrayConverted.length === 0) {
18 | return [];
19 | }
20 | const total = sum(arrayConverted);
21 | const average = avg(arrayConverted);
22 | const maximum = max(arrayConverted);
23 | const minimum = min(arrayConverted);
24 | return [total, average, maximum, minimum];
25 | }
26 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | {
9 | "compilerOptions": {
10 | "types": ["cypress", "@testing-library/cypress", "cypress-axe"],
11 | "target": "ES2020",
12 | "useDefineForClassFields": true,
13 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
14 | "module": "ESNext",
15 | "skipLibCheck": true,
16 |
17 | /* Bundler mode */
18 | "moduleResolution": "bundler",
19 | "allowImportingTsExtensions": true,
20 | "resolveJsonModule": true,
21 | "isolatedModules": true,
22 | "noEmit": true,
23 | "jsx": "react-jsx",
24 |
25 | /* Linting */
26 | "strict": true,
27 | "noUnusedLocals": true,
28 | "noUnusedParameters": true,
29 | "noFallthroughCasesInSwitch": true
30 | },
31 | "include": ["src"],
32 | "references": [{ "path": "./tsconfig.node.json" }]
33 | }
34 |
--------------------------------------------------------------------------------
/examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-demo",
3 | "version": "0.1.1",
4 | "private": true,
5 | "homepage": "https://feedzai.github.io/AutoVizuA11y/",
6 | "dependencies": {
7 | "@emotion/react": "^11.11.1",
8 | "@emotion/styled": "^11.11.0",
9 | "@feedzai/autovizua11y": "file:..",
10 | "@fontsource/outfit": "^5.0.13",
11 | "@mui/material": "^5.14.11",
12 | "@visx/visx": "^3.3.0",
13 | "d3-array": "^3.2.4",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0"
16 | },
17 | "scripts": {
18 | "dev": "vite",
19 | "build": "vite build",
20 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
21 | "preview": "vite preview",
22 | "predeploy": "npm run build",
23 | "deploy": "gh-pages -d dist"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | },
43 | "devDependencies": {
44 | "@types/react": "^18.2.66",
45 | "@types/react-dom": "^18.2.22",
46 | "@vitejs/plugin-react": "^4.2.1",
47 | "eslint": "^8.57.0",
48 | "eslint-plugin-react": "^7.34.1",
49 | "eslint-plugin-react-hooks": "^4.6.0",
50 | "eslint-plugin-react-refresh": "^0.4.6",
51 | "gh-pages": "^6.0.0",
52 | "vite": "^5.2.0"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/utils/toSafeClassname.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | /**
9 | * Converts the name of the series into a version suitable for a class
10 | *
11 | * @export
12 | * @param {string} multiSeriesValues - One of the multiserie string values
13 | * @return {string} - The string value refactored to fit a className
14 | */
15 | export function toSafeClassName(multiSeriesValues: string): string {
16 | // Remove leading and trailing whitespace
17 | let safeClassName = multiSeriesValues.trim();
18 |
19 | // Replace spaces and invalid characters with dashes
20 | safeClassName = safeClassName.replace(/[^a-zA-Z0-9_-]/g, "-");
21 |
22 | // Replace multiple consecutive dashes with a single dash
23 | safeClassName = safeClassName.replace(/-+/g, "-");
24 |
25 | return "series_" + safeClassName;
26 | }
27 |
--------------------------------------------------------------------------------
/cypress/support/commands.ts:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************
3 | // This example commands.ts shows you how to
4 | // create various custom commands and overwrite
5 | // existing commands.
6 | //
7 | // For more comprehensive examples of custom
8 | // commands please read more here:
9 | // https://on.cypress.io/custom-commands
10 | // ***********************************************
11 | //
12 | //
13 | // -- This is a parent command --
14 | // Cypress.Commands.add('login', (email, password) => { ... })
15 | //
16 | //
17 | // -- This is a child command --
18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
19 | //
20 | //
21 | // -- This is a dual command --
22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
23 | //
24 | //
25 | // -- This will overwrite an existing command --
26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
27 | //
28 | // declare global {
29 | // namespace Cypress {
30 | // interface Chainable {
31 | // login(email: string, password: string): Chainable
32 | // drag(subject: string, options?: Partial): Chainable
33 | // dismiss(subject: string, options?: Partial): Chainable
34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
35 | // }
36 | // }
37 | // }
38 | import "@testing-library/cypress/add-commands";
39 |
--------------------------------------------------------------------------------
/src/assets/style/AutoVizuA11y.css:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | .focused {
9 | outline: 2px solid blue;
10 | }
11 |
12 | .focused .a11y_desc:focus {
13 | outline: none; /* Remove default focus outline */
14 | }
15 |
16 | .visually-hidden {
17 | position: absolute;
18 | position: absolute !important;
19 | width: 1px !important;
20 | height: 1px !important;
21 | padding: 0 !important;
22 | margin: -1px !important;
23 | overflow: hidden !important;
24 | clip: rect(0, 0, 0, 0) !important;
25 | white-space: nowrap !important;
26 | border: 0 !important;
27 | }
28 |
29 | .a11y_nav_guide {
30 | position: fixed;
31 | z-index: 1000;
32 | left: 0;
33 | top: 0;
34 | width: 100%;
35 | height: 100%;
36 | overflow: auto;
37 | background-color: var(--shortcut-guide-background-color);
38 | color: var(--shortcut-guide-color-primary);
39 | }
40 |
--------------------------------------------------------------------------------
/examples/src/components/custom_shortcut_guide/components/CustomGuideDescription.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import * as constants from "../../../../../src/constants";
9 | import React from "react";
10 |
11 | export const CustomShortcutGuideDescription = () => (
12 |
16 | AutoVizually shortcut guide. AutoVizually lets you navigate between charts and underlying data
17 | elements using just the keyboard. When focused on a chart, a description regarding the data will
18 | be provided — you might receive a notification indicating that the chart description was
19 | produced by an AI model. For JAWS and NVDA users, it is recommended to turn Focus mode before
20 | navigating the data using the arrow keys.
21 |
22 | );
23 |
--------------------------------------------------------------------------------
/examples/src/components/custom_shortcut_guide/components/CustomGuideHeader.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import * as constants from "../../../../../src/constants";
9 | import React from "react";
10 |
11 | export const CustomShortcutGuideHeader = ({ onClose }: { onClose: () => void }) => (
12 |
13 |
17 | Custom Shortcut Guide
18 |
19 |
20 | Question Mark or Escape
21 |
22 |
29 |
30 | );
31 |
--------------------------------------------------------------------------------
/src/utils/wiper.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import React from "react";
9 |
10 | import * as constants from "./../constants";
11 |
12 | /**
13 | * Wipes attributes from the chart and underlying data elements.
14 | *
15 | * @export
16 | * @param {React.RefObject} chartRef - Reference to the chart element.
17 | * @param {boolean} [first=false] - Whether this is the first run.
18 | */
19 | export function wiper(chartRef: React.RefObject, first: boolean = false): void {
20 | const chart = chartRef.current;
21 | if (!chart) return;
22 | if (first) {
23 | constants.ATTRIBUTES_TO_REMOVE.forEach((attr) => {
24 | chart.querySelectorAll(`[${attr}]`).forEach((element) => {
25 | element.removeAttribute(attr);
26 | });
27 | });
28 | } else {
29 | chart.querySelectorAll(constants.TABINDEX_ZERO).forEach((element) => {
30 | element.removeAttribute("tabindex");
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/shortcut_guide/components/NativeGuideHeader.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import * as constants from "../../../constants";
9 | import type { TFunction } from "i18next";
10 |
11 | export const ShortcutGuideHeader = ({ onClose, t }: { onClose: () => void; t: TFunction }) => {
12 | return (
13 |
14 |
18 | {t("sg_title")}
19 |
20 |
21 | Question Mark or Escape
22 |
23 |
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for considering contributing to our project!
4 |
5 | **New to contributing?** Check out this free series [How to Contribute to an Open Source Project on GitHub][egghead] to learn how.
6 |
7 | ## Contact the team
8 |
9 | If you want to discuss AutoVizuA11y in greater depth, **or contribute to the project**, contact us through [data-viz@feedzai.com](mailto:data-viz@feedzai.com).
10 |
11 | ## Project Setup
12 |
13 | 1. Fork and clone the repository.
14 | 2. Run `$ npm install` to install dependencies.
15 | 3. Create a new branch for your pull request (PR).
16 |
17 | > **Tip:** Keep your `main` branch synchronized with the original repository. To set this up, run:
18 | >
19 | > ```bash
20 | > git remote add upstream https://github.com/feedzai/AutoVizuA11y
21 | > git fetch upstream
22 | > git branch --set-upstream-to=upstream/master main
23 | > ```
24 | >
25 | > This ensures that your `main` branch tracks the upstream repository's master branch, facilitating easier updates. Base your pull request branches on this `main` branch.
26 |
27 | ## Test your changes
28 |
29 | Before committing your changes, ensure all tests pass.
30 |
31 | ## Assistance Required
32 |
33 | Check out the [open issues][issues] for tasks that need assistance. Please follow one of the provided templates when adding an issue to this repository.
34 |
35 | Also, please consider watching the repository and actively participating in discussions, responding to questions, bug reports, and feature requests. Your engagement is appreciated!
36 |
37 |
38 | [egghead]: https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github
39 | [issues]: https://github.com/feedzai/AutoVizuA11y/issues
40 |
41 |
--------------------------------------------------------------------------------
/cypress/e2e/internationalization.cy.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | describe("Internationalization Tests", () => {
3 | it("should use default English (en-GB) language when internationalization property is empty", () => {
4 | cy.visit("/");
5 | cy.injectAxe();
6 | cy.findByTestId("manual-descriptions-option").click();
7 | cy.wait(500);
8 | cy.findAllByTestId("a11y_desc").first().focus();
9 | cy.focused().type("{alt}k");
10 | cy.findAllByTestId("a11y-chart-alert").first().should("contain.text", "The average is");
11 | cy.focused().type("?");
12 | cy.get("dialog").should("be.visible");
13 | cy.get("dialog h2").should("contain.text", "Shortcut guide");
14 | cy.focused().type("{esc}");
15 | cy.checkA11y();
16 | });
17 |
18 | it("should use default Portuguese (pt-PT) language when language property is pt-PT", () => {
19 | cy.visit("/");
20 | cy.injectAxe();
21 | cy.findByTestId("manual-descriptions-option").click();
22 | cy.wait(500);
23 | cy.findAllByTestId("a11y_desc").first().focus().tab().tab();
24 | cy.focused().type("{alt}k");
25 | cy.findAllByTestId("a11y-chart-alert").eq(2).should("contain.text", "A média é");
26 | cy.focused().type("?");
27 | cy.get("dialog").should("be.visible");
28 | cy.get("dialog h2").should("contain.text", "Guia de atalhos");
29 | cy.checkA11y();
30 | });
31 |
32 | it("should use an overriten en-GB string", () => {
33 | cy.visit("/");
34 | cy.injectAxe();
35 | cy.findByTestId("manual-descriptions-option").click();
36 | cy.wait(500);
37 | cy.findAllByTestId("a11y_desc").first().focus().tab().tab();
38 | cy.focused().type("?");
39 | cy.get("dialog").should("be.visible");
40 | cy.get("dialog h2").should("contain.text", "Overwritten Shortcut guide title");
41 | cy.checkA11y();
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/examples/src/Options.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Card from "@mui/material/Card";
3 | import CardContent from "@mui/material/CardContent";
4 | import { CardActionArea } from "@mui/material";
5 | import "./assets/style/Options.css";
6 | import Typography from "@mui/material/Typography";
7 |
8 | const Options = ({ setAutomatic, setManual }) => {
9 | const automaticChosen = () => {
10 | setAutomatic(true);
11 | };
12 | const manualChosen = () => {
13 | setManual(true);
14 | };
15 | return (
16 | <>
17 |
18 |
19 |
20 |
21 |
Option A
Automatic Descriptions
22 |
23 | The descriptions for each chart are generated by an OpenAI model or one accessible
24 | via an OpenAI-compatible API. Because of that,{" "}
25 | an OpenAI or OpenAI-compatible API key is needed.
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
Option B
34 |
Manual Descriptions
35 |
36 | The descriptions for each chart were previously generated using an OpenAI model.
37 | They were then manually added to each chart.{" "}
38 | No OpenAI or OpenAI-compatible API key is needed.
39 |
40 |
41 |
42 |
43 |
44 | >
45 | );
46 | };
47 | export default Options;
48 |
--------------------------------------------------------------------------------
/src/utils/arrayConverter.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | /**
9 | * Converts the data object into an array with only the numerical values.
10 | *
11 | * @export
12 | * @template T
13 | * @param {T[]} data - The array of data objects to convert.
14 | * @param {keyof T} insight - The key of the numerical value to extract.
15 | * @return {Promise} Promise resolving to an array with numerical values.
16 | * @throws {Error} If the insight key does not exist in the data objects or if the value is not a number.
17 | */
18 | export function arrayConverter>(
19 | data: T[],
20 | insight: keyof T,
21 | ): number[] {
22 | try {
23 | if (insight === "") {
24 | return [];
25 | }
26 | if (!insight || typeof insight !== "string") {
27 | throw new Error("Invalid insight key provided");
28 | }
29 | return data.map((item, index) => {
30 | const value = item[insight];
31 | if (typeof value !== "number") {
32 | throw new Error(`Invalid value at index ${index}: expected number, got ${typeof value}`);
33 | }
34 | return value;
35 | });
36 | } catch (error) {
37 | console.error("Error in arrayConverter:", error);
38 | throw error;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import { resolve } from "node:path";
9 | import { defineConfig } from "vite";
10 | import dts from "vite-plugin-dts";
11 | import tsconfigPaths from "vite-tsconfig-paths";
12 | import react from "@vitejs/plugin-react-swc";
13 | import { libInjectCss } from "vite-plugin-lib-inject-css";
14 |
15 | // https://vitejs.dev/config/
16 | export default defineConfig({
17 | plugins: [
18 | react(),
19 | dts({
20 | insertTypesEntry: true,
21 | }),
22 | tsconfigPaths(),
23 | libInjectCss(),
24 | ],
25 | build: {
26 | lib: {
27 | entry: resolve(__dirname, "src/index.ts"),
28 | name: "AutoVizuA11y",
29 | formats: ["es", "umd"],
30 | fileName: (format) => {
31 | const OUTPUT: Partial> = {
32 | es: "index.es.mjs",
33 | umd: "index.umd.cjs",
34 | };
35 |
36 | return OUTPUT[format] ?? "index.umd.cjs";
37 | },
38 | },
39 | rollupOptions: {
40 | external: ["react", "react-dom", "react/jsx-runtime"],
41 | output: {
42 | globals: {
43 | react: "React",
44 | "react-dom": "ReactDOM",
45 | "react/jsx-runtime": "react/jsx-runtime",
46 | "@feedzai/js-utilities": "JSUtilities",
47 | },
48 | },
49 | },
50 | },
51 | });
52 |
--------------------------------------------------------------------------------
/src/utils/processData.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import { arrayConverter } from "./arrayConverter";
9 | import { insightsCalculator } from "./insightsCalculator";
10 |
11 | interface ProcessDataProps {
12 | data: Record[];
13 | validatedInsights: string;
14 | setArrayConverted: (data: number[]) => void;
15 | setInsightsArray: (data: number[]) => void;
16 | }
17 |
18 | /**
19 | * Processes the data, by setting the values of the calculated insights
20 | *
21 | * @export
22 | * @param {Record[]} data - Chart data.
23 | * @param {string} validatedInsights - Data key from which insights should be calculated.
24 | * @param {Function} setArrayConverted - Setter function for the converted array of values.
25 | * @param {Function} setInsightsArray - Setter function for the converted array of insights.
26 | * @return {number} Average of the data values.
27 | */
28 |
29 | export function processData({
30 | data,
31 | validatedInsights,
32 | setArrayConverted,
33 | setInsightsArray,
34 | }: ProcessDataProps) {
35 | const dataConverted = arrayConverter(data, validatedInsights);
36 | setArrayConverted(dataConverted);
37 |
38 | let average = 0;
39 | if (validatedInsights !== "") {
40 | const insightsArrayAux = insightsCalculator(dataConverted);
41 | setInsightsArray(insightsArrayAux);
42 |
43 | average = insightsArrayAux[1];
44 | }
45 |
46 | return average;
47 | }
48 |
--------------------------------------------------------------------------------
/examples/src/components/custom_shortcut_guide/components/CustomShortcutGuide.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import * as constants from "../../../../../src/constants";
9 | import React from "react";
10 | import { CustomShortcutGuideDescription } from "./CustomGuideDescription";
11 | import { CustomShortcutGuideHeader } from "./CustomGuideHeader";
12 | import { CustomShortcutGuideBody } from "./CustomShortcutGuideBody";
13 |
14 | interface CustomShortcutGuideProps {
15 | dialogRef: React.RefObject;
16 | }
17 |
18 | /**
19 | * Component that renders the list of all AutoVizuA11y shortcuts.
20 | *
21 | * @return Shortcut guide.
22 | */
23 | const CustomShortcutGuide = ({ dialogRef }: CustomShortcutGuideProps): JSX.Element => {
24 | const handleCloseDialog = () => {
25 | const dialog = dialogRef.current;
26 | if (dialog) {
27 | dialog.close();
28 | }
29 | };
30 |
31 | return (
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default CustomShortcutGuide;
47 |
--------------------------------------------------------------------------------
/src/components/shortcut_guide/components/NativeShortcutGuide.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import "../../../assets/style/NativeShortcutGuide.css";
9 |
10 | import * as constants from "../../../constants";
11 | import { ShortcutGuideDescription } from "./NativeGuideDescription";
12 | import { ShortcutGuideHeader } from "./NativeGuideHeader";
13 | import { ShortcutGuideBody } from "./NativeShortcutGuideBody";
14 | import type { TFunction } from "i18next";
15 |
16 | interface NativeShortcutGuideProps {
17 | dialogRef: React.RefObject;
18 | t: TFunction;
19 | }
20 |
21 | /**
22 | * Component that renders the list of all AutoVizuA11y shortcuts.
23 | *
24 | * @return Shortcut guide.
25 | */
26 | export const NativeShortcutGuide = ({ dialogRef, t }: NativeShortcutGuideProps): JSX.Element => {
27 | const handleCloseDialog = () => {
28 | const dialog = dialogRef.current;
29 | if (dialog) {
30 | dialog.close();
31 | }
32 | };
33 |
34 | return (
35 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/src/components/shortcut_guide/components/NativeShortcutGuideSection.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import { Section } from "../../../assets/data/GuideData";
9 | import * as constants from "../../../constants";
10 | import type { TFunction } from "i18next";
11 |
12 | interface ShortcutGuideSectionProps {
13 | section: Section;
14 | sectionIndex: number;
15 | t: TFunction;
16 | }
17 |
18 | export const ShortcutGuideSection = ({ section, sectionIndex, t }: ShortcutGuideSectionProps) => {
19 | return (
20 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/examples/src/components/custom_shortcut_guide/components/CustomShortcutGuideSection.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import { Section } from "../../../../../src/assets/data/GuideData";
9 | import * as constants from "../../../../../src/constants";
10 | import React from "react";
11 |
12 | interface CustomShortcutGuideSectionProps {
13 | section: Section;
14 | sectionIndex: number;
15 | }
16 |
17 | export const CustomShortcutGuideSection = ({
18 | section,
19 | sectionIndex,
20 | }: CustomShortcutGuideSectionProps) => (
21 |
50 | );
51 |
--------------------------------------------------------------------------------
/src/components/descriptions/DescriptionsKeyHandler.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import { template } from "@feedzai/js-utilities";
9 |
10 | type AutoDescriptionsProps = {
11 | dynamicDescriptions?: boolean;
12 | apiKey: string;
13 | model?: string;
14 | temperature?: number;
15 | };
16 |
17 | interface DescriptionsKeyHandlerParams {
18 | chartRef: React.RefObject;
19 | setDescriptionContent: (value: string) => void;
20 | type: string;
21 | descs: string[];
22 | title: string;
23 | autoDescriptions?: AutoDescriptionsProps;
24 | event?: React.KeyboardEvent;
25 | }
26 |
27 | /**
28 | * Handles the longer and shorter description change when Alt (option) + B or Alt (option) + S are pressed, respectively.
29 | *
30 | * @export
31 | */
32 | export function descriptionsKeyHandler({
33 | chartRef,
34 | setDescriptionContent,
35 | type,
36 | descs,
37 | title,
38 | autoDescriptions,
39 | event,
40 | }: DescriptionsKeyHandlerParams): void {
41 | if (!chartRef.current) return;
42 |
43 | const isItAutomatic = autoDescriptions ? "Automatic description: " : "";
44 |
45 | const fullTitle = template("{{title}}, {{type}}. {{isItAutomatic}}", {
46 | title,
47 | type,
48 | isItAutomatic,
49 | });
50 | if (!event) {
51 | setDescriptionContent(fullTitle + descs[1]);
52 | return;
53 | }
54 | if (event.altKey) {
55 | switch (event.code) {
56 | case "KeyS":
57 | setDescriptionContent(fullTitle + descs[1]);
58 | break;
59 | case "KeyB":
60 | setDescriptionContent(fullTitle + descs[0]);
61 | break;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/utils/handleFirstFocus.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import React from "react";
9 | import { wait, getLSItem, setLSItem } from "@feedzai/js-utilities";
10 |
11 | import * as constants from "./../constants";
12 | import { TFunction } from "i18next";
13 |
14 | interface HandleFirstFocusProps {
15 | alertDiv: React.ReactNode | null;
16 | chartRef: React.RefObject;
17 | alertDivRef: React.RefObject;
18 | t: TFunction<"translation", undefined>;
19 | }
20 |
21 | /**
22 | * Handles the first focus of an AutoVizuA11y chart.
23 | *
24 | * @export
25 | * @param {React.ReactNode | null} alertDiv - Div where the alerts are set.
26 | * @param {React.RefObject} chartRef - React reference of the chart.
27 | * @param {React.RefObject} alertDivRef - React reference of the alertDiv.
28 | * @param {TFunction<"translation", undefined>} t - Translation function from useTranslation hook.
29 | */
30 | export async function handleFirstFocus({
31 | alertDiv,
32 | chartRef,
33 | alertDivRef,
34 | t,
35 | }: HandleFirstFocusProps) {
36 | const chart = chartRef.current;
37 | const alertElement = alertDivRef.current;
38 | if (!chart || !alertElement) {
39 | console.warn("Chart or alert element not found");
40 | return;
41 | }
42 | chart.classList.add(constants.FOCUS_CLASS);
43 | const toolTutorial = getLSItem(constants.TOOL_TUTORIAL_KEY);
44 | if (toolTutorial === "true" && alertDiv) {
45 | alertElement.textContent = t("alert");
46 | await wait(constants.ALERT_DURATION);
47 | alertElement.textContent = "\u00A0";
48 | setLSItem(constants.TOOL_TUTORIAL_KEY, "false");
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/navigation/Skip.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import React from "react";
9 | import { getElements } from "../../utils/getElements";
10 |
11 | interface SkipParams {
12 | event: React.KeyboardEvent;
13 | chartRef: React.RefObject;
14 | selectorType: {
15 | element?: string;
16 | className?: string;
17 | };
18 | selectedSeries: string;
19 | }
20 |
21 | /**
22 | * Enables the navigation to the end/beginning of a chart.
23 | *
24 | * @param {SkipParams} params - The parameters for the skip function.
25 | */
26 | export function skip({ event, chartRef, selectorType, selectedSeries }: SkipParams): void {
27 | const elements = getElements({ chartRef, selectorType, selectedSeries });
28 | const activeElement = document.activeElement as HTMLElement | null;
29 |
30 | if (!activeElement || !elements.includes(activeElement)) {
31 | return;
32 | }
33 | const { nativeEvent } = event;
34 |
35 | if (isSkipToBeginning(nativeEvent)) {
36 | nativeEvent.preventDefault();
37 | elements[0]?.focus();
38 | } else if (isSkipToEnd(nativeEvent)) {
39 | nativeEvent.preventDefault();
40 | elements[elements.length - 1]?.focus();
41 | }
42 | }
43 |
44 | /**
45 | * Checks if the Q or Home keys are being pressed.
46 | */
47 | function isSkipToBeginning(nativeEvent: Event): boolean {
48 | return (
49 | nativeEvent instanceof KeyboardEvent &&
50 | ((nativeEvent.altKey && nativeEvent.code === "KeyQ") || nativeEvent.code === "Home")
51 | );
52 | }
53 |
54 | /**
55 | * Checks if the W or End keys are being pressed.
56 | */
57 | function isSkipToEnd(nativeEvent: Event): boolean {
58 | return (
59 | nativeEvent instanceof KeyboardEvent &&
60 | ((nativeEvent.altKey && nativeEvent.code === "KeyW") || nativeEvent.code === "End")
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/src/utils/getElements.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | interface SelectorType {
9 | element?: string;
10 | className?: string;
11 | }
12 |
13 | interface GetElementsParams {
14 | chartRef: React.RefObject;
15 | selectorType?: SelectorType;
16 | selectedSeries?: string | string[];
17 | }
18 |
19 | /**
20 | * Get elements based on selector type and selected series.
21 | *
22 | * @export
23 | * @param {Object} params - The parameters for getting elements.
24 | * @param {React.RefObject} params.chartRef - The container to search for elements.
25 | * @param {SelectorType} [params.selectorType] - Type of selector to use.
26 | * @param {string | string[]} [params.selectedSeries] - Selected series to filter elements.
27 | * @return {HTMLElement[]} Array with HTML elements representing the chart data.
28 | */
29 | export function getElements({
30 | chartRef,
31 | selectorType,
32 | selectedSeries,
33 | }: GetElementsParams): HTMLElement[] {
34 | if (!chartRef.current) return [];
35 |
36 | let elements: NodeListOf | HTMLCollectionOf;
37 |
38 | if (selectorType?.element) {
39 | elements = chartRef.current.querySelectorAll(selectorType.element);
40 | } else if (selectorType?.className) {
41 | elements = chartRef.current.getElementsByClassName(selectorType.className);
42 | } else {
43 | console.warn("No valid selector type provided");
44 | return [];
45 | }
46 |
47 | const elementsArray = Array.from(elements) as HTMLElement[];
48 |
49 | if (!selectedSeries || (Array.isArray(selectedSeries) && selectedSeries.length === 0)) {
50 | return elementsArray;
51 | }
52 |
53 | const seriesArray = Array.isArray(selectedSeries) ? selectedSeries : [selectedSeries];
54 |
55 | return elementsArray.filter((element) =>
56 | seriesArray.some((series) => element.classList.contains(series)),
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/shortcut_guide/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
3 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
4 | * You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
5 | * Other licensing options may be available, please reach out to data-viz@feedzai.com for more information.
6 | */
7 |
8 | import { guideKeyHandler } from "../navigation";
9 | import * as constants from "../../constants";
10 | import { NativeShortcutGuide } from "./components/NativeShortcutGuide";
11 | import { cloneValidElement } from "@feedzai/js-utilities";
12 | import type { TFunction } from "i18next";
13 |
14 | interface ShortcutGuideContainerProps {
15 | shortcutGuide: React.ReactElement | undefined;
16 | shortcutGuideRef: React.RefObject;
17 | setIsShortcutGuideOpen: (bool: boolean) => void;
18 | t: TFunction;
19 | }
20 |
21 | /**
22 | * Component that renders the a ShortcutGuide.
23 | *
24 | * @export
25 | * @param {JSX.Element | undefined} shortcutGuide - A custom ShortcutGuide.
26 | * @param {React.RefObject} shortcutGuideRef - The React reference to this shortcut guide.
27 | * @param {(bool: boolean) => void} setIsShortcutGuideOpen - Setter function that deals with the logic of opening the guide.
28 | * @return Either the native ShortcutGuide or a custom one.
29 | */
30 | export const ShortcutGuideContainer = ({
31 | shortcutGuide,
32 | shortcutGuideRef,
33 | setIsShortcutGuideOpen,
34 | t,
35 | }: ShortcutGuideContainerProps): JSX.Element => {
36 | return (
37 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/examples/src/Homepage.jsx:
--------------------------------------------------------------------------------
1 | import "./assets/style/Homepage.css";
2 | import { useState } from "react";
3 | import Options from "./Options";
4 | import KeyRequest from "./KeyRequest";
5 | import CardGrid from "./components/automatic/ChartGrid";
6 | import CardGridManual from "./components/manual/ChartGridManual";
7 |
8 | function Homepage() {
9 | const [apiKey, setApiKey] = useState("");
10 | const [model, setModel] = useState("gpt-3.5-turbo");
11 | const [baseUrl, setBaseUrl] = useState("https://api.openai.com/v1/");
12 | const [isValid, setIsValid] = useState(false);
13 | const [automatic, setAutomatic] = useState(false);
14 | const [manual, setManual] = useState(false);
15 | const [home, setHome] = useState(true);
16 |
17 | const goBack = () => {
18 | setHome(true);
19 | setAutomatic(false);
20 | setManual(false);
21 | setIsValid(false);
22 | };
23 |
24 | return (
25 |
26 |
AutoVizuA11y — examples
27 | {home === false ? (
28 |
31 | ) : (
32 |
33 | {" "}
34 | Choose one of the options to access a gallery of charts built using{" "}
35 |
36 | AutoVizuA11y
37 | {" "}
38 |