From 6fb9c6509385974a32c5a4a1bb79d2254ec68f1f Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Fri, 21 Jul 2023 12:43:40 +0100 Subject: [PATCH 01/68] initial commit, single scatter plot --- src/plot_api/accessibility.js | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/plot_api/accessibility.js diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js new file mode 100644 index 00000000000..76b841daab6 --- /dev/null +++ b/src/plot_api/accessibility.js @@ -0,0 +1,48 @@ +import {c2mChart} from "chart2music"; + +export function enable(gd) { + + var fullLayout = gd._fullLayout; + var fullData = gd._fullData; + + const c2mData = {}; + + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + if(trace.type === 'scatter') { + c2mData[i] = trace.y; + } + else { + Lib.warn('Accessibility not implemented for trace type: ' + trace.type); + return; + } + } + + var closed_captions = document.createElement('div'); + closed_captions.id = 'cc'; + gd.appendChild(closed_captions); + + c2mChart({ + title: fullLayout.title.text ?? "", + type: "line", + axes: { + x: { + label: fullLayout.xaxis.title.text ?? "" + }, + y: { + label: fullLayout.yaxis.title.text ?? "" + } + }, + element: gd, + cc: closed_captions, + data: c2mData, + options: { + onFocusCallback: ({slice, index}) => { + Plotly.Fx.hover(gd, [{ + curveNumber: slice, + pointNumber: index + }]) + } + } + }); +}; \ No newline at end of file From 87b4c5c40ebad0e63bdbcfdcdd14f6be0e5963a7 Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Fri, 21 Jul 2023 12:52:40 +0100 Subject: [PATCH 02/68] single scatter plot --- package-lock.json | 14 ++++++++++++++ package.json | 1 + src/plot_api/plot_api.js | 8 ++++++-- src/plot_api/plot_config.js | 8 +++++++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 94be8de512b..645ffc8522b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@turf/bbox": "^6.4.0", "@turf/centroid": "^6.0.2", "canvas-fit": "^1.5.0", + "chart2music": "^1.11.0", "color-alpha": "1.0.4", "color-normalize": "1.5.0", "color-parse": "1.3.8", @@ -3801,6 +3802,14 @@ "node": ">=4.0.0" } }, + "node_modules/chart2music": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/chart2music/-/chart2music-1.11.0.tgz", + "integrity": "sha512-6iePd63Nn6EN/QuUkdctk9clxgzX+lslGeUSJvzVhWS94QZFX504WXYokDbqo9F8CIf8S1syis41XH/EEW2R7w==", + "engines": { + "node": ">16.14" + } + }, "node_modules/check-node-version": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-4.2.1.tgz", @@ -15728,6 +15737,11 @@ "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", "dev": true }, + "chart2music": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/chart2music/-/chart2music-1.11.0.tgz", + "integrity": "sha512-6iePd63Nn6EN/QuUkdctk9clxgzX+lslGeUSJvzVhWS94QZFX504WXYokDbqo9F8CIf8S1syis41XH/EEW2R7w==" + }, "check-node-version": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-4.2.1.tgz", diff --git a/package.json b/package.json index 245a0e74487..00cb6d843e5 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "@turf/bbox": "^6.4.0", "@turf/centroid": "^6.0.2", "canvas-fit": "^1.5.0", + "chart2music": "^1.11.0", "color-alpha": "1.0.4", "color-normalize": "1.5.0", "color-parse": "1.3.8", diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 7088f08eb3d..46334d5a8c2 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -26,6 +26,7 @@ var manageArrays = require('./manage_arrays'); var helpers = require('./helpers'); var subroutines = require('./subroutines'); var editTypes = require('./edit_types'); +var accessibility = require('./accessibility'); var AX_NAME_PATTERN = require('../plots/cartesian/constants').AX_NAME_PATTERN; @@ -376,10 +377,13 @@ function _doPlot(gd, data, layout, config) { // calculated. Would be much better to separate margin calculations from // component drawing - see https://github.com/plotly/plotly.js/issues/2704 Plots.doAutoMargin, - saveRangeInitialForInsideTickLabels, - Plots.previousPromises + saveRangeInitialForInsideTickLabels ); + if(gd._context.accessible) seq.push(accessibility.enable); + + seq.push(Plots.previousPromises); + function saveRangeInitialForInsideTickLabels(gd) { if(gd._fullLayout._insideTickLabelsAutorange) { if(graphWasEmpty) Axes.saveRangeInitial(gd, true); diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 577710ac3ac..f3d605b76bc 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -471,7 +471,13 @@ var configAttributes = { 'instead of MM/DD/YYYY). Currently `grouping` and `currency` are ignored', 'for our automatic number formatting, but can be used in custom formats.' ].join(' ') - } + }, + + accessible: { + valType: 'boolean', + dflt: true, + description: 'Whether or not to enable chart2music keyboard and sound accessibility', + }, }; var dfltConfig = {}; From 64ae50593d929871c9f62b1fc33aaabdc9543698 Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Fri, 21 Jul 2023 13:50:27 +0100 Subject: [PATCH 03/68] options and info --- src/plot_api/accessibility.js | 7 +++++-- src/plot_api/plot_api.js | 2 +- src/plot_api/plot_config.js | 16 ++++++++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 76b841daab6..efc2e8ce987 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -20,6 +20,7 @@ export function enable(gd) { var closed_captions = document.createElement('div'); closed_captions.id = 'cc'; + closed_captions.className = 'closed_captions'; gd.appendChild(closed_captions); c2mChart({ @@ -42,7 +43,9 @@ export function enable(gd) { curveNumber: slice, pointNumber: index }]) - } - } + }, + ...gd._context.chart2musicOptions + }, + info: gd._context.chart2musicInfo }); }; \ No newline at end of file diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 46334d5a8c2..be233a740f7 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -380,7 +380,7 @@ function _doPlot(gd, data, layout, config) { saveRangeInitialForInsideTickLabels ); - if(gd._context.accessible) seq.push(accessibility.enable); + if(gd._context.chart2music) seq.push(accessibility.enable); seq.push(Plots.previousPromises); diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index f3d605b76bc..9e0e4a0b0f4 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -473,11 +473,23 @@ var configAttributes = { ].join(' ') }, - accessible: { + chart2music: { valType: 'boolean', - dflt: true, + dflt: false, description: 'Whether or not to enable chart2music keyboard and sound accessibility', }, + + chart2musicOptions: { + valType: 'any', + dflt: {}, + description: 'Additional options to pass to the chart2music API. See https://www.chart2music.com/docs/API/Config', + }, + + chart2musicInfo: { + valType: 'any', + dflt: {}, + description: 'Info to pass to the chart2music API. See https://www.chart2music.com/docs/API/Config', + }, }; var dfltConfig = {}; From d37d24f04e99b8aba492a453c069bdeeb8f654ef Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Fri, 21 Jul 2023 14:10:47 +0100 Subject: [PATCH 04/68] data and trace labels --- src/plot_api/accessibility.js | 17 +++++++++++++++-- src/plot_api/plot_config.js | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index efc2e8ce987..81751f27dfe 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -7,10 +7,21 @@ export function enable(gd) { const c2mData = {}; + var labels = []; for(var i = 0; i < fullData.length; i++) { var trace = fullData[i]; if(trace.type === 'scatter') { - c2mData[i] = trace.y; + var traceData = []; + for(var p = 0; p < trace.y.length; p++) { + traceData.push( + { + x: trace.x[p], + y: trace.y[p], + label: trace.text[p] + }) + } + c2mData[trace.name ?? i] = traceData; + labels.push(trace.name ?? i); } else { Lib.warn('Accessibility not implemented for trace type: ' + trace.type); @@ -23,6 +34,8 @@ export function enable(gd) { closed_captions.className = 'closed_captions'; gd.appendChild(closed_captions); + Lib.warn(fullLayout); + Lib.warn(fullData); c2mChart({ title: fullLayout.title.text ?? "", type: "line", @@ -40,7 +53,7 @@ export function enable(gd) { options: { onFocusCallback: ({slice, index}) => { Plotly.Fx.hover(gd, [{ - curveNumber: slice, + curveNumber: labels.indexOf(slice), pointNumber: index }]) }, diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 9e0e4a0b0f4..3ac614d0458 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -475,7 +475,7 @@ var configAttributes = { chart2music: { valType: 'boolean', - dflt: false, + dflt: true, description: 'Whether or not to enable chart2music keyboard and sound accessibility', }, From d0467bbfe94d8565d7e51e790a68dca01ed07b48 Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Fri, 21 Jul 2023 14:20:48 +0100 Subject: [PATCH 05/68] false default --- src/plot_api/plot_config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 3ac614d0458..9e0e4a0b0f4 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -475,7 +475,7 @@ var configAttributes = { chart2music: { valType: 'boolean', - dflt: true, + dflt: false, description: 'Whether or not to enable chart2music keyboard and sound accessibility', }, From 3a73776e6a5acbfdecffbea62c13b8586c30a20e Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Fri, 21 Jul 2023 14:22:34 +0100 Subject: [PATCH 06/68] remove debugging prints --- src/plot_api/accessibility.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 81751f27dfe..1b2486f1351 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -24,7 +24,7 @@ export function enable(gd) { labels.push(trace.name ?? i); } else { - Lib.warn('Accessibility not implemented for trace type: ' + trace.type); + Lib.error('Accessibility not implemented for trace type: ' + trace.type); return; } } @@ -33,9 +33,7 @@ export function enable(gd) { closed_captions.id = 'cc'; closed_captions.className = 'closed_captions'; gd.appendChild(closed_captions); - - Lib.warn(fullLayout); - Lib.warn(fullData); + c2mChart({ title: fullLayout.title.text ?? "", type: "line", From b4df95921c2cf0d473eb8be79e94b63c380170fc Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Fri, 21 Jul 2023 14:45:11 +0100 Subject: [PATCH 07/68] use index if no x provided --- src/plot_api/accessibility.js | 10 ++++++---- src/plot_api/plot_config.js | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 1b2486f1351..db6cea1544f 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -8,16 +8,18 @@ export function enable(gd) { const c2mData = {}; var labels = []; + for(var i = 0; i < fullData.length; i++) { var trace = fullData[i]; + Lib.warn(trace); if(trace.type === 'scatter') { var traceData = []; for(var p = 0; p < trace.y.length; p++) { traceData.push( { - x: trace.x[p], - y: trace.y[p], - label: trace.text[p] + x: trace.x ? trace.x[p] : p, + y: trace.y[p] ?? none, + label: trace.text[p] ?? p }) } c2mData[trace.name ?? i] = traceData; @@ -33,7 +35,7 @@ export function enable(gd) { closed_captions.id = 'cc'; closed_captions.className = 'closed_captions'; gd.appendChild(closed_captions); - + c2mChart({ title: fullLayout.title.text ?? "", type: "line", diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 9e0e4a0b0f4..3ac614d0458 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -475,7 +475,7 @@ var configAttributes = { chart2music: { valType: 'boolean', - dflt: false, + dflt: true, description: 'Whether or not to enable chart2music keyboard and sound accessibility', }, From 97b608f423bbdf350b36081d91ad2a2e26e9f3e1 Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Sat, 22 Jul 2023 09:27:52 +0100 Subject: [PATCH 08/68] Lib requirement --- src/plot_api/accessibility.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index db6cea1544f..5127cc18536 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,4 +1,5 @@ import {c2mChart} from "chart2music"; +var Lib = require('../lib'); export function enable(gd) { @@ -18,7 +19,7 @@ export function enable(gd) { traceData.push( { x: trace.x ? trace.x[p] : p, - y: trace.y[p] ?? none, + y: trace.y[p], label: trace.text[p] ?? p }) } From 6205d628f266961323d63d2bd5a3b569e89b6d52 Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Sat, 22 Jul 2023 10:51:08 +0100 Subject: [PATCH 09/68] layout destructuring --- src/plot_api/accessibility.js | 49 +++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 5127cc18536..2c317a18a62 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,33 +1,32 @@ -import {c2mChart} from "chart2music"; -var Lib = require('../lib'); +"use strict"; +import c2mChart from "chart2music"; -export function enable(gd) { - - var fullLayout = gd._fullLayout; - var fullData = gd._fullData; +export function enable (gd) { const c2mData = {}; - - var labels = []; + const labels = []; + + const fullData = gd._fullData; for(var i = 0; i < fullData.length; i++) { var trace = fullData[i]; - Lib.warn(trace); if(trace.type === 'scatter') { var traceData = []; - for(var p = 0; p < trace.y.length; p++) { - traceData.push( - { - x: trace.x ? trace.x[p] : p, - y: trace.y[p], - label: trace.text[p] ?? p - }) + if ('y' in trace) { + for(var p = 0; p < trace.y.length; p++) { + traceData.push( + { + x: trace.x ? trace.x[p] : p, + y: trace.y[p], + label: trace.text[p] ?? p + }) + } + c2mData[trace.name ?? i] = traceData; + labels.push(trace.name ?? i); } - c2mData[trace.name ?? i] = traceData; - labels.push(trace.name ?? i); } else { - Lib.error('Accessibility not implemented for trace type: ' + trace.type); + // 'Accessibility not implemented for trace type: ' + trace.type return; } } @@ -37,15 +36,21 @@ export function enable(gd) { closed_captions.className = 'closed_captions'; gd.appendChild(closed_captions); + const { + title: {text: title_text = ''}, + xaxis: {title: {text: xaxis_text = ''}}, + yaxis: {title: {text: yaxis_text = ''}}, + } = gd._fullLayout; + c2mChart({ - title: fullLayout.title.text ?? "", + title: title_text, type: "line", axes: { x: { - label: fullLayout.xaxis.title.text ?? "" + label: xaxis_text }, y: { - label: fullLayout.yaxis.title.text ?? "" + label: yaxis_text } }, element: gd, From 7e40cdee223ff15045f121f73f4ef1a8315742e4 Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Sat, 22 Jul 2023 11:10:31 +0100 Subject: [PATCH 10/68] layout defaults --- src/plot_api/accessibility.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 2c317a18a62..3d267936918 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,5 +1,6 @@ "use strict"; -import c2mChart from "chart2music"; +// import c2mChart from "chart2music"; +var c2mChart = require("chart2music"); export function enable (gd) { @@ -7,7 +8,7 @@ export function enable (gd) { const labels = []; const fullData = gd._fullData; - + for(var i = 0; i < fullData.length; i++) { var trace = fullData[i]; if(trace.type === 'scatter') { @@ -37,9 +38,9 @@ export function enable (gd) { gd.appendChild(closed_captions); const { - title: {text: title_text = ''}, - xaxis: {title: {text: xaxis_text = ''}}, - yaxis: {title: {text: yaxis_text = ''}}, + title: {text: title_text = ''} = {}, + xaxis: {title: {text: xaxis_text = ''} = {}} = {}, + yaxis: {title: {text: yaxis_text = ''} = {}} = {}, } = gd._fullLayout; c2mChart({ From 6d9185b5f857850fe2b01e1f6d960e5f773e5eb0 Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Sat, 22 Jul 2023 11:25:36 +0100 Subject: [PATCH 11/68] destructure trace --- src/plot_api/accessibility.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 3d267936918..00197c9f2a5 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,6 +1,6 @@ "use strict"; -// import c2mChart from "chart2music"; -var c2mChart = require("chart2music"); +import c2mChart from "chart2music"; +// var c2mChart = require("chart2music"); export function enable (gd) { @@ -11,19 +11,20 @@ export function enable (gd) { for(var i = 0; i < fullData.length; i++) { var trace = fullData[i]; - if(trace.type === 'scatter') { + var {type, x = [], y = [], name = i, text = []} = trace; + if(type === 'scatter') { var traceData = []; if ('y' in trace) { - for(var p = 0; p < trace.y.length; p++) { + for(var p = 0; p < y.length; p++) { traceData.push( { - x: trace.x ? trace.x[p] : p, - y: trace.y[p], - label: trace.text[p] ?? p + x: x.length > 0 ? x[p] : p, + y: y[p], + label: text[p] ?? p }) } - c2mData[trace.name ?? i] = traceData; - labels.push(trace.name ?? i); + c2mData[name] = traceData; + labels.push(name); } } else { From 935cf54f272c407bfb448020eec782311b6a2cb1 Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Sat, 22 Jul 2023 12:07:28 +0100 Subject: [PATCH 12/68] test sourcetype module --- .eslintrc | 3 ++- src/plot_api/accessibility.js | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.eslintrc b/.eslintrc index b852aafd41d..8c5623e6e95 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,8 @@ "eslint:recommended" ], "parserOptions": { - "ecmaVersion": 5 + "ecmaVersion": 5, + "sourceType": "module" }, "env": { "commonjs": true diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 00197c9f2a5..428c3dfaf19 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,6 +1,5 @@ "use strict"; import c2mChart from "chart2music"; -// var c2mChart = require("chart2music"); export function enable (gd) { @@ -10,7 +9,7 @@ export function enable (gd) { const fullData = gd._fullData; for(var i = 0; i < fullData.length; i++) { - var trace = fullData[i]; + var trace = fullData[i] ?? {}; var {type, x = [], y = [], name = i, text = []} = trace; if(type === 'scatter') { var traceData = []; From 44deae2dac11f27ed51d83653094385141445dac Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Sat, 22 Jul 2023 12:17:07 +0100 Subject: [PATCH 13/68] remove sourcetype --- .eslintrc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.eslintrc b/.eslintrc index 8c5623e6e95..b852aafd41d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,8 +4,7 @@ "eslint:recommended" ], "parserOptions": { - "ecmaVersion": 5, - "sourceType": "module" + "ecmaVersion": 5 }, "env": { "commonjs": true From c38ab672f66ce72ba36804f7f842432b8afd7bfc Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Fri, 28 Jul 2023 08:34:50 +0100 Subject: [PATCH 14/68] into es5 --- src/plot_api/accessibility.js | 5 +++-- webpack.config.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 428c3dfaf19..a7bfb3434f7 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,7 +1,8 @@ "use strict"; -import c2mChart from "chart2music"; -export function enable (gd) { +var c2mChart = require("chart2music"); + +module.exports = function enable (gd) { const c2mData = {}; const labels = []; diff --git a/webpack.config.js b/webpack.config.js index cf24ca8e46b..276d33be63e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -22,7 +22,7 @@ module.exports = { } }, { test: /\.js$/, - include: /node_modules[\\\/](buffer|d3-color|d3-interpolate|is-mobile)[\\\/]/, + include: /node_modules[\\\/](buffer|d3-color|d3-interpolate|is-mobile|chart2music)[\\\/]/, use: { loader: 'babel-loader', options: { From c6a27be5acdb1213418e8f100a6b05ec568773a1 Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Fri, 28 Jul 2023 09:28:59 +0100 Subject: [PATCH 15/68] include mjs files --- webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 276d33be63e..cd38f138168 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,8 +21,8 @@ module.exports = { loader: 'babel-loader' } }, { - test: /\.js$/, - include: /node_modules[\\\/](buffer|d3-color|d3-interpolate|is-mobile|chart2music)[\\\/]/, + test: /\.(js|mjs)$/, + include: /node_modules[\\\/](buffer|chart2music|d3-color|d3-interpolate|is-mobile)[\\\/]/, use: { loader: 'babel-loader', options: { From cac174c19222b7e7956fcd3ad41180f80f80a62f Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Sun, 30 Jul 2023 15:46:10 +0100 Subject: [PATCH 16/68] transpile accessibility --- src/plot_api/accessibility.js | 11 ++++++----- webpack.config.js | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index a7bfb3434f7..0c5cd6754dc 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,8 +1,8 @@ "use strict"; -var c2mChart = require("chart2music"); +import c2mChart from "chart2music"; -module.exports = function enable (gd) { +export function enable (gd) { const c2mData = {}; const labels = []; @@ -30,8 +30,8 @@ module.exports = function enable (gd) { else { // 'Accessibility not implemented for trace type: ' + trace.type return; - } - } + }; + }; var closed_captions = document.createElement('div'); closed_captions.id = 'cc'; @@ -68,5 +68,6 @@ module.exports = function enable (gd) { ...gd._context.chart2musicOptions }, info: gd._context.chart2musicInfo - }); + } + ); }; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index cd38f138168..a3f295e14b7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,8 +21,21 @@ module.exports = { loader: 'babel-loader' } }, { - test: /\.(js|mjs)$/, - include: /node_modules[\\\/](buffer|chart2music|d3-color|d3-interpolate|is-mobile)[\\\/]/, + test: /\.js$/, + include: /node_modules[\\\/](buffer|d3-color|d3-interpolate|is-mobile)[\\\/]/, + use: { + loader: 'babel-loader', + options: { + babelrc: false, + configFile: false, + presets: [ + '@babel/preset-env' + ] + } + }, + }, { + test: /accessibility\.js$/, + include: /src[\\\/]plot_api/, use: { loader: 'babel-loader', options: { From 159ca1bf3c5ab81bf1c3d77b87aec873cfff0e09 Mon Sep 17 00:00:00 2001 From: Alistair-Welch Date: Sun, 30 Jul 2023 16:20:54 +0100 Subject: [PATCH 17/68] accessibility in config --- src/plot_api/accessibility.js | 129 ++++++++++++++++++---------------- src/plot_api/plot_api.js | 2 +- src/plot_api/plot_config.js | 24 +++---- 3 files changed, 80 insertions(+), 75 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 0c5cd6754dc..a5784e1ba7d 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -2,72 +2,83 @@ import c2mChart from "chart2music"; +const supportedAccessibilityLibraries = ['chart2music']; + export function enable (gd) { - const c2mData = {}; - const labels = []; - - const fullData = gd._fullData; + const {library, options} = gd._context.accessibility; + if (!supportedAccessibilityLibraries.includes(library)) { + // 'Accessibility not implemented for library: ' + library + return; + } + + if (library === 'chart2music') { + const c2mData = {}; + const labels = []; + const info = options.info; + delete options.info; + const fullData = gd._fullData; - for(var i = 0; i < fullData.length; i++) { - var trace = fullData[i] ?? {}; - var {type, x = [], y = [], name = i, text = []} = trace; - if(type === 'scatter') { - var traceData = []; - if ('y' in trace) { - for(var p = 0; p < y.length; p++) { - traceData.push( - { - x: x.length > 0 ? x[p] : p, - y: y[p], - label: text[p] ?? p - }) + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i] ?? {}; + var {type, x = [], y = [], name = i, text = []} = trace; + if(type === 'scatter') { + var traceData = []; + if ('y' in trace) { + for(var p = 0; p < y.length; p++) { + traceData.push( + { + x: x.length > 0 ? x[p] : p, + y: y[p], + label: text[p] ?? p + }) + } + c2mData[name] = traceData; + labels.push(name); } - c2mData[name] = traceData; - labels.push(name); } - } - else { - // 'Accessibility not implemented for trace type: ' + trace.type - return; + else { + // 'Accessibility not implemented for trace type: ' + trace.type + return; + }; }; - }; - - var closed_captions = document.createElement('div'); - closed_captions.id = 'cc'; - closed_captions.className = 'closed_captions'; - gd.appendChild(closed_captions); + + var closed_captions = document.createElement('div'); + closed_captions.id = 'cc'; + closed_captions.className = 'closed_captions'; + gd.appendChild(closed_captions); - const { - title: {text: title_text = ''} = {}, - xaxis: {title: {text: xaxis_text = ''} = {}} = {}, - yaxis: {title: {text: yaxis_text = ''} = {}} = {}, - } = gd._fullLayout; - - c2mChart({ - title: title_text, - type: "line", - axes: { - x: { - label: xaxis_text + const { + title: {text: title_text = ''} = {}, + xaxis: {title: {text: xaxis_text = ''} = {}} = {}, + yaxis: {title: {text: yaxis_text = ''} = {}} = {}, + } = gd._fullLayout; + + c2mChart({ + title: title_text, + type: "line", + axes: { + x: { + label: xaxis_text + }, + y: { + label: yaxis_text + } }, - y: { - label: yaxis_text - } - }, - element: gd, - cc: closed_captions, - data: c2mData, - options: { - onFocusCallback: ({slice, index}) => { - Plotly.Fx.hover(gd, [{ - curveNumber: labels.indexOf(slice), - pointNumber: index - }]) + element: gd, + cc: closed_captions, + data: c2mData, + options: { + onFocusCallback: ({slice, index}) => { + Plotly.Fx.hover(gd, [{ + curveNumber: labels.indexOf(slice), + pointNumber: index + }]) + }, + ...options }, - ...gd._context.chart2musicOptions - }, - info: gd._context.chart2musicInfo - } - ); + info: info + } + ); + }; }; \ No newline at end of file diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index be233a740f7..964d989884d 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -380,7 +380,7 @@ function _doPlot(gd, data, layout, config) { saveRangeInitialForInsideTickLabels ); - if(gd._context.chart2music) seq.push(accessibility.enable); + if(gd._context.accessibility.enabled) seq.push(accessibility.enable); seq.push(Plots.previousPromises); diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 3ac614d0458..7f1bdac0c81 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -473,22 +473,16 @@ var configAttributes = { ].join(' ') }, - chart2music: { - valType: 'boolean', - dflt: true, - description: 'Whether or not to enable chart2music keyboard and sound accessibility', - }, - - chart2musicOptions: { - valType: 'any', - dflt: {}, - description: 'Additional options to pass to the chart2music API. See https://www.chart2music.com/docs/API/Config', - }, - - chart2musicInfo: { + accessibility: { valType: 'any', - dflt: {}, - description: 'Info to pass to the chart2music API. See https://www.chart2music.com/docs/API/Config', + dflt: { + library: 'chart2music', + enabled: true, + options: {info: {}} + }, + description: ['Accessibility options: which library to use; whether to enable and the options to pass to the library.', + 'chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ' + ].join(' ') }, }; From 08b820bbd0b41b359cd4df9db89b9c37885496ea Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Fri, 12 Jan 2024 18:54:37 -0500 Subject: [PATCH 18/68] Remove special rules from webpack.config: I agree with the contributor's choice of dialetc over plotly's default. However, I think adding piece-meal rules to change how stuff is built will ultimately make it more complicated to refactor, maintain, and read in the future. The current dialect of javascript (enforced primarily by the linter at least in the pull-request stage), is limiting and inconvenient. Migrating to a more modern javascript, I believe, requires a broader strategy. --- webpack.config.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index a3f295e14b7..cf24ca8e46b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -33,19 +33,6 @@ module.exports = { ] } }, - }, { - test: /accessibility\.js$/, - include: /src[\\\/]plot_api/, - use: { - loader: 'babel-loader', - options: { - babelrc: false, - configFile: false, - presets: [ - '@babel/preset-env' - ] - } - }, }, { test: /\.glsl$/, include: /node_modules/, From 41b0fb9a1c80efad07e8947c0297188e96e4dea6 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Fri, 12 Jan 2024 18:58:44 -0500 Subject: [PATCH 19/68] Reorder declarations to make a bit more readable --- src/plot_api/plot_api.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 9484e943247..a44c77ef274 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -382,19 +382,20 @@ function _doPlot(gd, data, layout, config) { // happens outside of marginPushers where all the other automargins are // calculated. Would be much better to separate margin calculations from // component drawing - see https://github.com/plotly/plotly.js/issues/2704 - Plots.doAutoMargin, - saveRangeInitialForInsideTickLabels + Plots.doAutoMargin ); - if(gd._context.accessibility.enabled) seq.push(accessibility.enable); - - seq.push(Plots.previousPromises); - function saveRangeInitialForInsideTickLabels(gd) { if(gd._fullLayout._insideTickLabelsAutorange) { if(graphWasEmpty) Axes.saveRangeInitial(gd, true); } } + seq.push(saveRangeInitialForInsideTickLabels(gd)); + + if(gd._context.accessibility.enabled) seq.push(accessibility.enable); + + seq.push(Plots.previousPromises); + // even if everything we did was synchronous, return a promise // so that the caller doesn't care which route we took From 94e25c377f88132f3b5e16e7a7313429af7ac66b Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Fri, 12 Jan 2024 19:02:02 -0500 Subject: [PATCH 20/68] Lint --- src/plot_api/plot_api.js | 2 +- src/plot_api/plot_config.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index a44c77ef274..4162ddc7211 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -390,7 +390,7 @@ function _doPlot(gd, data, layout, config) { if(graphWasEmpty) Axes.saveRangeInitial(gd, true); } } - seq.push(saveRangeInitialForInsideTickLabels(gd)); + seq.push(saveRangeInitialForInsideTickLabels); if(gd._context.accessibility.enabled) seq.push(accessibility.enable); diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 7f1bdac0c81..993397eb871 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -481,8 +481,8 @@ var configAttributes = { options: {info: {}} }, description: ['Accessibility options: which library to use; whether to enable and the options to pass to the library.', - 'chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ' - ].join(' ') + 'chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ' + ].join(' ') }, }; From 61ea8cec89cb583ea0dd02aa6748b7a98e5cff7f Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 16 Jan 2024 19:03:37 -0500 Subject: [PATCH 21/68] Change import/export scheme to promise --- src/plot_api/accessibility.js | 75 ++++++++++++++++++++--------------- src/plot_api/plot_api.js | 3 ++ webpack.config.js | 11 +++++ 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index a5784e1ba7d..b300a696cd4 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,18 +1,19 @@ "use strict"; -import c2mChart from "chart2music"; +var underlyingModule = import('chart2music'); -const supportedAccessibilityLibraries = ['chart2music']; -export function enable (gd) { +const supportedAccessibilityLibraries = ['chart2music']; +function enable(gd) { const {library, options} = gd._context.accessibility; if (!supportedAccessibilityLibraries.includes(library)) { // 'Accessibility not implemented for library: ' + library return; } - + console.log("We're enabled!"); if (library === 'chart2music') { + console.log("Library === chart2music"); const c2mData = {}; const labels = []; const info = options.info; @@ -23,6 +24,7 @@ export function enable (gd) { var trace = fullData[i] ?? {}; var {type, x = [], y = [], name = i, text = []} = trace; if(type === 'scatter') { + console.log("Found a scatter"); var traceData = []; if ('y' in trace) { for(var p = 0; p < y.length; p++) { @@ -33,52 +35,59 @@ export function enable (gd) { label: text[p] ?? p }) } + console.log("TraceData.length: " + String(traceData.length)); c2mData[name] = traceData; labels.push(name); } } else { // 'Accessibility not implemented for trace type: ' + trace.type + console.log("Accessibility not implemented for trace type."); return; }; }; - + var closed_captions = document.createElement('div'); closed_captions.id = 'cc'; closed_captions.className = 'closed_captions'; - gd.appendChild(closed_captions); + gd.appendChild(closed_captions); // this does get generated const { title: {text: title_text = ''} = {}, xaxis: {title: {text: xaxis_text = ''} = {}} = {}, yaxis: {title: {text: yaxis_text = ''} = {}} = {}, - } = gd._fullLayout; - - c2mChart({ - title: title_text, - type: "line", - axes: { - x: { - label: xaxis_text + } = gd._fullLayout; // I do not understand this notation that well anyway. + + return underlyingModule.then( function(mod) { + console.log("Got underlying module!"); + console.log(mod); + var ret = mod.c2mChart({ + title: title_text, + type: "line", + axes: { + x: { + label: xaxis_text + }, + y: { + label: yaxis_text + } }, - y: { - label: yaxis_text - } - }, - element: gd, - cc: closed_captions, - data: c2mData, - options: { - onFocusCallback: ({slice, index}) => { - Plotly.Fx.hover(gd, [{ - curveNumber: labels.indexOf(slice), - pointNumber: index - }]) + element: gd, + cc: closed_captions, + data: c2mData, + options: { + onFocusCallback: ({slice, index}) => { + Plotly.Fx.hover(gd, [{ + curveNumber: labels.indexOf(slice), + pointNumber: index + }]) + }, + ...options }, - ...options - }, - info: info - } - ); + info: info + }); + console.log(ret); + }); }; -}; \ No newline at end of file +}; +module.exports = { enable }; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 4162ddc7211..b85c5955261 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -393,6 +393,9 @@ function _doPlot(gd, data, layout, config) { seq.push(saveRangeInitialForInsideTickLabels); if(gd._context.accessibility.enabled) seq.push(accessibility.enable); + seq.push(function(gd) { + console.log("This should come after the thing is senabled"); + }); seq.push(Plots.previousPromises); diff --git a/webpack.config.js b/webpack.config.js index cf24ca8e46b..5a3dba177db 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -20,6 +20,17 @@ module.exports = { use: { loader: 'babel-loader' } + }, { + test: /\.mjs$/, + include: /node_modules[\\\/]chart2music[\\\/]/, + use: { + loader: 'babel-loader', + options: { + babelrc: false, + configFile: false, + presets: ['@babel/preset-env',], + }, + }, }, { test: /\.js$/, include: /node_modules[\\\/](buffer|d3-color|d3-interpolate|is-mobile)[\\\/]/, From c9a0b91306d80aea333ccd01bab63d772a582a6d Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 16 Jan 2024 19:25:44 -0500 Subject: [PATCH 22/68] Remove a console.log --- src/plot_api/plot_api.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index b85c5955261..4162ddc7211 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -393,9 +393,6 @@ function _doPlot(gd, data, layout, config) { seq.push(saveRangeInitialForInsideTickLabels); if(gd._context.accessibility.enabled) seq.push(accessibility.enable); - seq.push(function(gd) { - console.log("This should come after the thing is senabled"); - }); seq.push(Plots.previousPromises); From cc926394e1114532c0b7c1de5cf9850b2e93ae9c Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 16 Jan 2024 19:59:01 -0500 Subject: [PATCH 23/68] Limit chunks to 1 --- webpack.config.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 5a3dba177db..880186303c5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,3 +1,4 @@ +var webpack = require('webpack'); var path = require('path'); var NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); @@ -63,7 +64,10 @@ module.exports = { } }, plugins: [ - new NodePolyfillPlugin({ includeAliases: ['process'] }) + new NodePolyfillPlugin({ includeAliases: ['process'] }), + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), ], watchOptions: { ignored: [ From 7baa0d3570a6962c858f453abe23199fbbe2af73 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 17 Jan 2024 16:54:58 -0500 Subject: [PATCH 24/68] Upgrade chart2music to support commonjs --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index b457280029a..60046e2afe3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@turf/centroid": "^6.0.2", "base64-arraybuffer": "^1.0.2", "canvas-fit": "^1.5.0", - "chart2music": "^1.11.0", + "chart2music": "^1.13.0", "color-alpha": "1.0.4", "color-normalize": "1.5.0", "color-parse": "1.3.8", @@ -3819,9 +3819,9 @@ } }, "node_modules/chart2music": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/chart2music/-/chart2music-1.11.0.tgz", - "integrity": "sha512-6iePd63Nn6EN/QuUkdctk9clxgzX+lslGeUSJvzVhWS94QZFX504WXYokDbqo9F8CIf8S1syis41XH/EEW2R7w==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/chart2music/-/chart2music-1.13.0.tgz", + "integrity": "sha512-AldPW7R5c2Zmgb0FydI423pgUm+G4y3pccl2/pNKd/GBPFZV6n0/G50b7V2R0yhb3vkyWRun5RZRvc6W9M8ayQ==", "engines": { "node": ">16.14" } @@ -15803,9 +15803,9 @@ "dev": true }, "chart2music": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/chart2music/-/chart2music-1.11.0.tgz", - "integrity": "sha512-6iePd63Nn6EN/QuUkdctk9clxgzX+lslGeUSJvzVhWS94QZFX504WXYokDbqo9F8CIf8S1syis41XH/EEW2R7w==" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/chart2music/-/chart2music-1.13.0.tgz", + "integrity": "sha512-AldPW7R5c2Zmgb0FydI423pgUm+G4y3pccl2/pNKd/GBPFZV6n0/G50b7V2R0yhb3vkyWRun5RZRvc6W9M8ayQ==" }, "check-node-version": { "version": "4.2.1", diff --git a/package.json b/package.json index 63f447d0310..ebb50cd32d1 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@turf/centroid": "^6.0.2", "base64-arraybuffer": "^1.0.2", "canvas-fit": "^1.5.0", - "chart2music": "^1.11.0", + "chart2music": "^1.13.0", "color-alpha": "1.0.4", "color-normalize": "1.5.0", "color-parse": "1.3.8", From 3127dee463a91cfc899d99e85426c64b3c48c807 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 17 Jan 2024 16:55:09 -0500 Subject: [PATCH 25/68] Rewrite accessibility in es5 --- src/plot_api/accessibility.js | 120 ++++++++++++++++------------------ 1 file changed, 55 insertions(+), 65 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index b300a696cd4..398060900ff 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,93 +1,83 @@ -"use strict"; +'use strict'; -var underlyingModule = import('chart2music'); +var c2m = require('chart2music'); +var Fx = require('../components/fx'); - -const supportedAccessibilityLibraries = ['chart2music']; +var supportedAccessibilityLibraries = ['chart2music']; function enable(gd) { - const {library, options} = gd._context.accessibility; - if (!supportedAccessibilityLibraries.includes(library)) { + var accessibilityVars = gd._context.accessibility; + var library = accessibilityVars.library; + var options = accessibilityVars.options; + if(!supportedAccessibilityLibraries.includes(library)) { // 'Accessibility not implemented for library: ' + library return; } - console.log("We're enabled!"); - if (library === 'chart2music') { - console.log("Library === chart2music"); - const c2mData = {}; - const labels = []; - const info = options.info; + if(library === 'chart2music') { + var c2mData = {}; + var labels = []; + var info = options.info; delete options.info; - const fullData = gd._fullData; + var fullData = gd._fullData; for(var i = 0; i < fullData.length; i++) { - var trace = fullData[i] ?? {}; - var {type, x = [], y = [], name = i, text = []} = trace; + var trace = fullData[i] ? fullData[i] : {}; + var type = trace.type; + var x = trace.x ? trace.x : []; + var y = trace.y ? trace.y : []; + var name = trace.name ? trace.name : i; + var text = trace.text ? trace.text : []; if(type === 'scatter') { - console.log("Found a scatter"); var traceData = []; - if ('y' in trace) { + if('y' in trace) { for(var p = 0; p < y.length; p++) { traceData.push( { x: x.length > 0 ? x[p] : p, y: y[p], - label: text[p] ?? p - }) + label: text[p] ? text[p] : p + }); } - console.log("TraceData.length: " + String(traceData.length)); c2mData[name] = traceData; labels.push(name); } - } - else { + } else { // 'Accessibility not implemented for trace type: ' + trace.type - console.log("Accessibility not implemented for trace type."); return; - }; - }; - - var closed_captions = document.createElement('div'); - closed_captions.id = 'cc'; - closed_captions.className = 'closed_captions'; - gd.appendChild(closed_captions); // this does get generated + } + } - const { - title: {text: title_text = ''} = {}, - xaxis: {title: {text: xaxis_text = ''} = {}} = {}, - yaxis: {title: {text: yaxis_text = ''} = {}} = {}, - } = gd._fullLayout; // I do not understand this notation that well anyway. + var closedCaptions = document.createElement('div'); + closedCaptions.id = 'cc'; + closedCaptions.className = 'closed_captions'; + gd.appendChild(closedCaptions); // this does get generated - return underlyingModule.then( function(mod) { - console.log("Got underlying module!"); - console.log(mod); - var ret = mod.c2mChart({ - title: title_text, - type: "line", - axes: { - x: { - label: xaxis_text - }, - y: { - label: yaxis_text - } + var titleText = gd._fullLayout.title.text ? gd._fullLayout.title.text : 'Chart'; + var xaxisText = gd._fullLayout.xaxis.title.text ? gd._fullLayout.xaxis.title.text : 'X Axis'; + var yaxisText = gd._fullLayout.yaxis.title.text ? gd._fullLayout.yaxis.title.text : 'Y Axis'; + options.onFocusCallback = function(dataInfo) { + Fx.hover(gd, [{ + curveNumber: labels.indexOf(dataInfo.slice), + pointNumber: dataInfo.index + }]); + }; + c2m.c2mChart({ + title: titleText, + type: 'line', + axes: { + x: { + label: xaxisText }, - element: gd, - cc: closed_captions, - data: c2mData, - options: { - onFocusCallback: ({slice, index}) => { - Plotly.Fx.hover(gd, [{ - curveNumber: labels.indexOf(slice), - pointNumber: index - }]) - }, - ...options + y: { + label: yaxisText }, - info: info - }); - console.log(ret); + }, + element: gd, + cc: closedCaptions, + data: c2mData, + options: options, + info: info }); - }; -}; -module.exports = { enable }; + } +} +exports.enable = enable; From 00572e86fd6ce2e6938154d46cab3e7fc1de615b Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 17 Jan 2024 17:30:38 -0500 Subject: [PATCH 26/68] Fix some undef errors in accessibility --- src/plot_api/accessibility.js | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 398060900ff..7c66b5ac904 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -23,10 +23,10 @@ function enable(gd) { for(var i = 0; i < fullData.length; i++) { var trace = fullData[i] ? fullData[i] : {}; var type = trace.type; - var x = trace.x ? trace.x : []; - var y = trace.y ? trace.y : []; - var name = trace.name ? trace.name : i; - var text = trace.text ? trace.text : []; + var x = trace.x !== undefined ? trace.x : []; + var y = trace.y !== undefined ? trace.y : []; + var name = trace.name !== undefined ? trace.name : i; + var text = trace.text !== undefined ? trace.text : []; if(type === 'scatter') { var traceData = []; if('y' in trace) { @@ -52,9 +52,25 @@ function enable(gd) { closedCaptions.className = 'closed_captions'; gd.appendChild(closedCaptions); // this does get generated - var titleText = gd._fullLayout.title.text ? gd._fullLayout.title.text : 'Chart'; - var xaxisText = gd._fullLayout.xaxis.title.text ? gd._fullLayout.xaxis.title.text : 'X Axis'; - var yaxisText = gd._fullLayout.yaxis.title.text ? gd._fullLayout.yaxis.title.text : 'Y Axis'; + var titleText = 'Chart'; + if((gd._fullLayout.title !== undefined) && (gd._fullLayout.title.text !== undefined)) { + titleText = gd._fullLayout.title.text; + } + + var xAxisText = 'X Axis'; + if((gd._fullLayout.xaxis !== undefined) && + (gd._fullLayout.xaxis.title !== undefined) && + (gd._fullLayout.xaxis.title.text !== undefined)) { + xAxisText = gd._fullLayout.xaxis.title.text; + } + + var yAxisText = 'Y Axis'; + if((gd._fullLayout.yaxis !== undefined) && + (gd._fullLayout.yaxis.title !== undefined) && + (gd._fullLayout.yaxis.title.text !== undefined)) { + yAxisText = gd._fullLayout.yaxis.title.text; + } + options.onFocusCallback = function(dataInfo) { Fx.hover(gd, [{ curveNumber: labels.indexOf(dataInfo.slice), @@ -66,10 +82,10 @@ function enable(gd) { type: 'line', axes: { x: { - label: xaxisText + label: xAxisText }, y: { - label: yaxisText + label: yAxisText }, }, element: gd, From 30f4e111bff7c59941ed0544f3a605876d90ffa6 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 17 Jan 2024 17:41:19 -0500 Subject: [PATCH 27/68] update plot-schema diff --- test/plot-schema.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/plot-schema.json b/test/plot-schema.json index 9f5b82beb8a..e1572d79533 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -104,6 +104,17 @@ } }, "config": { + "accessibility": { + "description": "Accessibility options: which library to use; whether to enable and the options to pass to the library. chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ", + "dflt": { + "enabled": true, + "library": "chart2music", + "options": { + "info": {} + } + }, + "valType": "any" + }, "autosizable": { "description": "Determines whether the graphs are plotted with respect to layout.autosize:true and infer its container size.", "dflt": false, From d08adfbac774de9ee7c8406860452ee27cfab1c2 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 16:24:08 -0500 Subject: [PATCH 28/68] Add config so dev can place closed captions: Adding a closed caption div to plotly interefered with plotly's management of it. We now start by default outside of the generated plotly divs. But we add options to users can a) generate it with any id/class. Or specify that it already exists and use an existing element by id. --- src/plot_api/accessibility.js | 16 +++++++++++----- src/plot_api/plot_api.js | 2 -- src/plot_api/plot_config.js | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 7c66b5ac904..d4e1d6c33b9 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -17,7 +17,9 @@ function enable(gd) { var c2mData = {}; var labels = []; var info = options.info; + var closedCaptionsOptions = options.closedCaptions; delete options.info; + delete options.closedCaptions; var fullData = gd._fullData; for(var i = 0; i < fullData.length; i++) { @@ -46,11 +48,15 @@ function enable(gd) { return; } } - - var closedCaptions = document.createElement('div'); - closedCaptions.id = 'cc'; - closedCaptions.className = 'closed_captions'; - gd.appendChild(closedCaptions); // this does get generated + var closedCaptions; + if(closedCaptionsOptions.generate) { + closedCaptions = document.createElement('div'); // should this be Lib.getGraphDiv()? + closedCaptions.id = closedCaptionsOptions.elId; + closedCaptions.className = closedCaptionsOptions.elClassname; + gd.parentNode.insertBefore(closedCaptions, gd.nextSibling); // this does get generated + } else { + closedCaptions = document.getElementById(closedCaptionsOptions.elId); + } var titleText = 'Chart'; if((gd._fullLayout.title !== undefined) && (gd._fullLayout.title.text !== undefined)) { diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 4162ddc7211..755a2e4c746 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3663,7 +3663,6 @@ function deleteFrames(gd, frameList) { */ function purge(gd) { gd = Lib.getGraphDiv(gd); - var fullLayout = gd._fullLayout || {}; var fullData = gd._fullData || []; @@ -3681,7 +3680,6 @@ function purge(gd) { // in contrast to _doPlots.purge which does NOT clear _context! delete gd._context; - return gd; } diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 993397eb871..06ae1688bf2 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -478,7 +478,7 @@ var configAttributes = { dflt: { library: 'chart2music', enabled: true, - options: {info: {}} + options: {info: {}, closedCaptions: {generate: true, elId:'c2m-plotly-cc', elClassname:'c2m-plotly-closed_captions'}}, }, description: ['Accessibility options: which library to use; whether to enable and the options to pass to the library.', 'chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ' From e1f9563b05414b8c0f70192f6b44d4f33b02d87a Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 16:53:24 -0500 Subject: [PATCH 29/68] Add more default assurance in accessibility --- src/plot_api/accessibility.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index d4e1d6c33b9..158c1b4263a 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -49,6 +49,14 @@ function enable(gd) { } } var closedCaptions; + // redefaulting the defaults here from plot_config.js + // since during tests they don't seem to make it here + if(closedCaptionsOptions === undefined) { + closedCaptionsOptions = {}; + closedCaptionsOptions.generate=true; + closedCaptionsOptions.elId="c2m-plotly-cc"; + closedCaptionsOptions.elClassname="c2m-plotly-closed_captions"; + } if(closedCaptionsOptions.generate) { closedCaptions = document.createElement('div'); // should this be Lib.getGraphDiv()? closedCaptions.id = closedCaptionsOptions.elId; From 3f4ab549567af85a30c65de5e1bc0c141e2e4f10 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 16:58:46 -0500 Subject: [PATCH 30/68] Lint --- src/plot_api/accessibility.js | 6 +++--- src/plot_api/plot_config.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 158c1b4263a..080d11c12e2 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -53,9 +53,9 @@ function enable(gd) { // since during tests they don't seem to make it here if(closedCaptionsOptions === undefined) { closedCaptionsOptions = {}; - closedCaptionsOptions.generate=true; - closedCaptionsOptions.elId="c2m-plotly-cc"; - closedCaptionsOptions.elClassname="c2m-plotly-closed_captions"; + closedCaptionsOptions.generate = true; + closedCaptionsOptions.elId = 'c2m-plotly-cc'; + closedCaptionsOptions.elClassname = 'c2m-plotly-closed_captions'; } if(closedCaptionsOptions.generate) { closedCaptions = document.createElement('div'); // should this be Lib.getGraphDiv()? diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 06ae1688bf2..5f050a01f00 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -478,7 +478,7 @@ var configAttributes = { dflt: { library: 'chart2music', enabled: true, - options: {info: {}, closedCaptions: {generate: true, elId:'c2m-plotly-cc', elClassname:'c2m-plotly-closed_captions'}}, + options: {info: {}, closedCaptions: {generate: true, elId: 'c2m-plotly-cc', elClassname: 'c2m-plotly-closed_captions'}}, }, description: ['Accessibility options: which library to use; whether to enable and the options to pass to the library.', 'chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ' From 92d97b7a9a2b3aafd5eccd844b90ea547055e7c1 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 17:21:01 -0500 Subject: [PATCH 31/68] Skip empty trace data in accessibility.enable: Passing named empty arrays to chart2music makes it angry during validation. --- src/plot_api/accessibility.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 080d11c12e2..176c346d12d 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -40,6 +40,7 @@ function enable(gd) { label: text[p] ? text[p] : p }); } + if(traceData.length === 0) continue; c2mData[name] = traceData; labels.push(name); } @@ -77,7 +78,6 @@ function enable(gd) { (gd._fullLayout.xaxis.title.text !== undefined)) { xAxisText = gd._fullLayout.xaxis.title.text; } - var yAxisText = 'Y Axis'; if((gd._fullLayout.yaxis !== undefined) && (gd._fullLayout.yaxis.title !== undefined) && From 6b2dcc95198a5f4a546e9320c249299c774058d6 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 17:40:04 -0500 Subject: [PATCH 32/68] update plot-schema diff --- test/plot-schema.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/plot-schema.json b/test/plot-schema.json index e1572d79533..1df0a26d241 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -110,6 +110,11 @@ "enabled": true, "library": "chart2music", "options": { + "closedCaptions": { + "elClassname": "c2m-plotly-closed_captions", + "elId": "c2m-plotly-cc", + "generate": true + }, "info": {} } }, From b5bb51743115fe13ad225a8c0072d533f3e445d3 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 19:32:50 -0500 Subject: [PATCH 33/68] Modify how accessibility treats config vars: accessibility.js takes configuration variables from `plot_config.js`, modifies them, and passes them directly to `chart2music`. The problem is that assigning an object to a new variable doesn't copy it, so any change to that variable also changes the original object. Plotly uses changes (or lack thereof) in the config variables to decide if it can shortcut certain redraw steps. It cannot if config variables change. So if `accessibility.js` is run on every redraw, the config variables will look different (even if they are not), and plotly will always take the long way around during its rerendering. That being said, this solution here may need more thought. Questions like: when _should_ chart2music ask plotly to do a full redraw? Should it ever? Would it do that by changing a config it has access to? Will it ever change plotly config variables frequently. --- src/plot_api/accessibility.js | 18 ++++++++++-------- src/plot_api/plot_api.js | 2 -- src/plot_api/plot_config.js | 6 ++++-- test/jasmine/tests/transition_test.js | 7 +++---- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 176c346d12d..b0cd7a9ab9a 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,5 +1,6 @@ 'use strict'; +var Lib = require('../lib'); var c2m = require('chart2music'); var Fx = require('../components/fx'); @@ -9,6 +10,8 @@ function enable(gd) { var accessibilityVars = gd._context.accessibility; var library = accessibilityVars.library; var options = accessibilityVars.options; + var info = accessibilityVars.info; + var closedCaptionsOptions = accessibilityVars.closedCaptions; if(!supportedAccessibilityLibraries.includes(library)) { // 'Accessibility not implemented for library: ' + library return; @@ -16,12 +19,7 @@ function enable(gd) { if(library === 'chart2music') { var c2mData = {}; var labels = []; - var info = options.info; - var closedCaptionsOptions = options.closedCaptions; - delete options.info; - delete options.closedCaptions; var fullData = gd._fullData; - for(var i = 0; i < fullData.length; i++) { var trace = fullData[i] ? fullData[i] : {}; var type = trace.type; @@ -52,6 +50,7 @@ function enable(gd) { var closedCaptions; // redefaulting the defaults here from plot_config.js // since during tests they don't seem to make it here + // TODO: this commit maybe obsolete it if(closedCaptionsOptions === undefined) { closedCaptionsOptions = {}; closedCaptionsOptions.generate = true; @@ -84,8 +83,11 @@ function enable(gd) { (gd._fullLayout.yaxis.title.text !== undefined)) { yAxisText = gd._fullLayout.yaxis.title.text; } - - options.onFocusCallback = function(dataInfo) { + // Arguably should pass all config as copy to C2M + // If C2M eventually modifies them in any way (minus w/ _ prefix) + // It will always break transition/redraw logic in react + var options2 = Lib.extendDeepAll({}, options); + options2.onFocusCallback = function(dataInfo) { Fx.hover(gd, [{ curveNumber: labels.indexOf(dataInfo.slice), pointNumber: dataInfo.index @@ -105,7 +107,7 @@ function enable(gd) { element: gd, cc: closedCaptions, data: c2mData, - options: options, + options2: options, info: info }); } diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 755a2e4c746..7ad7d55c5da 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2676,7 +2676,6 @@ function react(gd, data, layout, config) { setPlotContext(gd, config); configChanged = diffConfig(oldConfig, gd._context); } - gd.data = data || []; helpers.cleanData(gd.data); gd.layout = layout || {}; @@ -2747,7 +2746,6 @@ function react(gd, data, layout, config) { Plots.doCalcdata(gd); subroutines.doAutoRangeAndConstraints(gd); - seq.push(function() { return Plots.transitionFromReact(gd, restyleFlags, relayoutFlags, oldFullLayout); }); diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 5f050a01f00..9cc5064d2d1 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -478,9 +478,11 @@ var configAttributes = { dflt: { library: 'chart2music', enabled: true, - options: {info: {}, closedCaptions: {generate: true, elId: 'c2m-plotly-cc', elClassname: 'c2m-plotly-closed_captions'}}, + options: {}, + info: {}, + closedCaptions: {generate: true, elId: 'c2m-plotly-cc', elClassname: 'c2m-plotly-closed_captions'} }, - description: ['Accessibility options: which library to use; whether to enable and the options to pass to the library.', + description: ['Accessibility options: which library to use; whether to enable, options to pass to the library, info to pass to the library, closedCaptions to control how plotly renders the closed-captions element.', 'chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ' ].join(' ') }, diff --git a/test/jasmine/tests/transition_test.js b/test/jasmine/tests/transition_test.js index ea42d3192b1..7344967b182 100644 --- a/test/jasmine/tests/transition_test.js +++ b/test/jasmine/tests/transition_test.js @@ -552,17 +552,16 @@ describe('Plotly.react transitions:', function() { .then(done, done.fail); }); - it('should not try to transition when the *config* has changed', function(done) { + fit('should not try to transition when the *config* has changed', function(done) { addSpies(); var data = [{y: [1, 2, 1]}]; var layout = {transition: {duration: 10}}; var config = {scrollZoom: true}; - Plotly.react(gd, data, layout, config) .then(function() { assertSpies('first draw', [ - [Plots, 'transitionFromReact', 0] + [Plots, 'transitionFromReact', 0] // so Plots.transitionFromReact should equal 0, ie didn't get called ]); }) .then(function() { @@ -581,7 +580,7 @@ describe('Plotly.react transitions:', function() { }) .then(function() { assertSpies('no config change', [ - [Plots, 'transitionFromReact', 1] + [Plots, 'transitionFromReact', 1] // react is not calling transitionFromReact, why? ]); }) .then(done, done.fail); From 25ca44a2f39b5f05baea69f599f3625ec671cc1c Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 19:44:42 -0500 Subject: [PATCH 34/68] Remove jasmine ftest focus --- test/jasmine/tests/transition_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jasmine/tests/transition_test.js b/test/jasmine/tests/transition_test.js index 7344967b182..83ec43edc7d 100644 --- a/test/jasmine/tests/transition_test.js +++ b/test/jasmine/tests/transition_test.js @@ -552,7 +552,7 @@ describe('Plotly.react transitions:', function() { .then(done, done.fail); }); - fit('should not try to transition when the *config* has changed', function(done) { + it('should not try to transition when the *config* has changed', function(done) { addSpies(); var data = [{y: [1, 2, 1]}]; From 73283245e3d9878b3a00efd12415885c22e2c1f2 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 19:44:50 -0500 Subject: [PATCH 35/68] Run schema plot diff --- test/plot-schema.json | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/plot-schema.json b/test/plot-schema.json index 1df0a26d241..05c263f96d0 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -105,18 +105,17 @@ }, "config": { "accessibility": { - "description": "Accessibility options: which library to use; whether to enable and the options to pass to the library. chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ", + "description": "Accessibility options: which library to use; whether to enable, options to pass to the library, info to pass to the library, closedCaptions to control how plotly renders the closed-captions element. chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ", "dflt": { + "closedCaptions": { + "elClassname": "c2m-plotly-closed_captions", + "elId": "c2m-plotly-cc", + "generate": true + }, "enabled": true, + "info": {}, "library": "chart2music", - "options": { - "closedCaptions": { - "elClassname": "c2m-plotly-closed_captions", - "elId": "c2m-plotly-cc", - "generate": true - }, - "info": {} - } + "options": {} }, "valType": "any" }, From 00b2750e49694828658fc8a684fca0a7b1e5eb02 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 19:45:48 -0500 Subject: [PATCH 36/68] Test if previous commits deprecate hack-fix --- src/plot_api/accessibility.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index b0cd7a9ab9a..640f57f8a6f 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -51,12 +51,6 @@ function enable(gd) { // redefaulting the defaults here from plot_config.js // since during tests they don't seem to make it here // TODO: this commit maybe obsolete it - if(closedCaptionsOptions === undefined) { - closedCaptionsOptions = {}; - closedCaptionsOptions.generate = true; - closedCaptionsOptions.elId = 'c2m-plotly-cc'; - closedCaptionsOptions.elClassname = 'c2m-plotly-closed_captions'; - } if(closedCaptionsOptions.generate) { closedCaptions = document.createElement('div'); // should this be Lib.getGraphDiv()? closedCaptions.id = closedCaptionsOptions.elId; From b04bb17a2d2a45ba9772beac446e2e3113aea1cb Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 20:14:14 -0500 Subject: [PATCH 37/68] Set c2music default to not make its caption div: I can't think of instance where 1) the caption div wouldn't be specified by the user 2) would maybe do some really fancy interaction w/ the graph 3) be completely invisible because its meant to be read outloud anyway Either way, the generation strategy needs work. * Tests now create their closed caption div --- src/plot_api/accessibility.js | 8 +++++--- src/plot_api/plot_config.js | 2 +- test/jasmine/assets/create_graph_div.js | 5 +++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 640f57f8a6f..8e3f90ae9fd 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -48,16 +48,18 @@ function enable(gd) { } } var closedCaptions; - // redefaulting the defaults here from plot_config.js - // since during tests they don't seem to make it here - // TODO: this commit maybe obsolete it if(closedCaptionsOptions.generate) { closedCaptions = document.createElement('div'); // should this be Lib.getGraphDiv()? closedCaptions.id = closedCaptionsOptions.elId; closedCaptions.className = closedCaptionsOptions.elClassname; gd.parentNode.insertBefore(closedCaptions, gd.nextSibling); // this does get generated + // TODO we need a better generator } else { closedCaptions = document.getElementById(closedCaptionsOptions.elId); + if(closedCaptions === null) { + // TODO maybe handle this better for the developer? + return; + } } var titleText = 'Chart'; diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 9cc5064d2d1..6eea766958a 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -480,7 +480,7 @@ var configAttributes = { enabled: true, options: {}, info: {}, - closedCaptions: {generate: true, elId: 'c2m-plotly-cc', elClassname: 'c2m-plotly-closed_captions'} + closedCaptions: {generate: false, elId: 'c2m-plotly-cc', elClassname: 'c2m-plotly-closed_captions'} }, description: ['Accessibility options: which library to use; whether to enable, options to pass to the library, info to pass to the library, closedCaptions to control how plotly renders the closed-captions element.', 'chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ' diff --git a/test/jasmine/assets/create_graph_div.js b/test/jasmine/assets/create_graph_div.js index 9791d46018c..ae6fcd39b52 100644 --- a/test/jasmine/assets/create_graph_div.js +++ b/test/jasmine/assets/create_graph_div.js @@ -5,6 +5,11 @@ module.exports = function createGraphDiv() { gd.id = 'graph'; document.body.appendChild(gd); + var closedCaptions = document.createElement('div'); + closedCaptions.id = 'c2m-plotly-cc'; + closedCaptions.className = 'c2m-plotly-closed_captions'; + document.body.appendChild(closedCaptions); // this does get generated + // force the graph to be at position 0,0 no matter what gd.style.position = 'fixed'; gd.style.left = 0; From d4bd71b8c3217bccd8bdcd5f7f072f0642372967 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 20:27:44 -0500 Subject: [PATCH 38/68] update plot-scheme diff --- test/plot-schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plot-schema.json b/test/plot-schema.json index 05c263f96d0..6da836214f5 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -110,7 +110,7 @@ "closedCaptions": { "elClassname": "c2m-plotly-closed_captions", "elId": "c2m-plotly-cc", - "generate": true + "generate": false }, "enabled": true, "info": {}, From af2ff80a04e1e3b7bfd90189f2f0f0f094996b5a Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 25 Jan 2024 20:46:12 -0500 Subject: [PATCH 39/68] Add closed captions to dev dashboard --- devtools/test_dashboard/index-mathjax3.html | 4 ++++ devtools/test_dashboard/index-mathjax3chtml.html | 4 ++++ devtools/test_dashboard/index-strict.html | 4 ++++ devtools/test_dashboard/index.html | 5 ++++- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/devtools/test_dashboard/index-mathjax3.html b/devtools/test_dashboard/index-mathjax3.html index c2365f1bd12..720633e5ca7 100644 --- a/devtools/test_dashboard/index-mathjax3.html +++ b/devtools/test_dashboard/index-mathjax3.html @@ -24,6 +24,10 @@

no MathJax: Apple: $2, Orange: $3

+
+
Closed Captions
+
Test
+
diff --git a/devtools/test_dashboard/index.html b/devtools/test_dashboard/index.html index 3f5ea288003..b162685b73b 100644 --- a/devtools/test_dashboard/index.html +++ b/devtools/test_dashboard/index.html @@ -20,7 +20,10 @@
- +
+
Closed Captions
+
Test
+
From 0c434076ff41708a827e5fda3caf798f42e3a622 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 27 Feb 2024 23:19:04 -0500 Subject: [PATCH 40/68] Refactor accessibility/c2m: 1) Since accessibility.js will support multiple accessibility libraries, there is now a C2M library that contains the API for initializing C2M to plotly charts. 2) The initialization process is split into it's various phases. 3) Trace types now have specific codecs to map plotly-C2M. See the README in each file. 4) xy-scatter codec was refactored a bit to not assume the x-y is domain-range. 5) All config variables are now copied from defaults so C2M doesn't mess with plotly's config obect. --- src/accessibility/README.md | 3 + src/accessibility/c2m/README.md | 33 +++++++ src/accessibility/c2m/all_codecs.js | 4 + src/accessibility/c2m/index.js | 114 ++++++++++++++++++++++ src/accessibility/c2m/xy_scatter_codec.js | 26 +++++ src/plot_api/accessibility.js | 113 +++------------------ 6 files changed, 191 insertions(+), 102 deletions(-) create mode 100644 src/accessibility/README.md create mode 100644 src/accessibility/c2m/README.md create mode 100644 src/accessibility/c2m/all_codecs.js create mode 100644 src/accessibility/c2m/index.js create mode 100644 src/accessibility/c2m/xy_scatter_codec.js diff --git a/src/accessibility/README.md b/src/accessibility/README.md new file mode 100644 index 00000000000..5db9ba55fa3 --- /dev/null +++ b/src/accessibility/README.md @@ -0,0 +1,3 @@ +# accesibility + +While anticipating more accesibility libraries in the future, as of today this is a trivial wrapper over the `c2m` directory. diff --git a/src/accessibility/c2m/README.md b/src/accessibility/c2m/README.md new file mode 100644 index 00000000000..d704ff32ef1 --- /dev/null +++ b/src/accessibility/c2m/README.md @@ -0,0 +1,33 @@ +# `plotly.js` wrapper for `c2m`, sonified charts + +This wrapper attaches a context object to `gd._context._c2m` with the following properties: + +* `.options`: the options object as required by c2m +* `.info`: the info object as required by c2m +* `.ccOptions`: information about where to place the closed caption div +* `.c2mHandler`: the object returned by calling the c2m library's initializer + +The first three have most of their values set by the defaults in *src/plot_api/plot_config.js*. + + +### index.js + +**index.js** exposes the following api: + +* `initC2M(gd, defaultConfig)` full resets the `c2mChart` object. + * `defaultConfig` is equal to `gd._context.accessibility`, as defined in *src/plot_api/plot_config.js*. + + +* `initClosedCaptionDiv(gd, config)` finds or creates the closed caption div, depending on `config`. + * `config` is equal to `defaultConfig.closedCaptions`. + +### all_codecs.js +**all_codecs.js** agregates all the individual **_codec.js* files, all are expect to export exactly two functions: `test` and `process`. + +I chose to aggregate all codecs in a separate file because it will ultimately be a 1-1 conversion if `plotly.js` adopts ES modules. + +## Writing a Codec + +This section is TODO + +chart2music supports N types of graphs. We should figure out how how to convert as many plotly types to chart2music types. I will write descriptions for each type in the folder, if not the actual code, along with what `test` must return and what `process` must return. diff --git a/src/accessibility/c2m/all_codecs.js b/src/accessibility/c2m/all_codecs.js new file mode 100644 index 00000000000..ec7aea5faa6 --- /dev/null +++ b/src/accessibility/c2m/all_codecs.js @@ -0,0 +1,4 @@ + +var xy_scatter = require('./xy_scatter_codec'); + +exports.codecs = [ xy_scatter ]; diff --git a/src/accessibility/c2m/index.js b/src/accessibility/c2m/index.js new file mode 100644 index 00000000000..a27c21adbd4 --- /dev/null +++ b/src/accessibility/c2m/index.js @@ -0,0 +1,114 @@ +var c2m = require('chart2music'); +var Fx = require('../../components/fx'); + +var codecs = require('./all_codecs').codecs; +// var codecs = [ require('./xy_scatter_codec') ] +// I intentionally don't do that because +// a) it's not possible in ESM w/ `import {}` +// b) may break weak tree shakers who bail if `require()` not top-level + +function initC2M(gd, defaultConfig) { + // TODO: should this be Lib.getGraphDiv()? + // TODO what is there besides a fullReset? + // TODO Do we need the capacity to add data (live listen?) + // TODO Do we need the capacity to reset all data + var c2mContext = gd._context._c2m = {}; + c2mContext.options = Lib.extendDeepAll({}, defaultConfig.options); + c2mContext.info = Lib.extendDeepAll({}, defaultConfig.info); + c2mContext.ccOptions = Lib.extendDeepAll({}, defaultConfig.closedCaptions); + + c2mContext.options.onFocusCallback = function(dataInfo) { + Fx.hover(gd, [{ + curveNumber: labels.indexOf(dataInfo.slice), + pointNumber: dataInfo.index + }]); + }; + + var titleText = 'Chart'; + if((gd._fullLayout.title !== undefined) && (gd._fullLayout.title.text !== undefined)) { + titleText = gd._fullLayout.title.text; + } + + var ccElement = initClosedCaptionDiv(gd, c2mContext.ccOptions); + + // TODO: + // I believe that title OR title.text could contain the information needed + // So we should stop if title is a type string + // Furthermore, I think that the traces would have to point to their axis, + // since it might not be x1, could be x2, etc + // So this really needs to be part of process() + var xAxisText = 'X Axis'; + if((gd._fullLayout.xaxis !== undefined) && + (gd._fullLayout.xaxis.title !== undefined) && + (gd._fullLayout.xaxis.title.text !== undefined)) { + xAxisText = gd._fullLayout.xaxis.title.text; + } + var yAxisText = 'Y Axis'; + if((gd._fullLayout.yaxis !== undefined) && + (gd._fullLayout.yaxis.title !== undefined) && + (gd._fullLayout.yaxis.title.text !== undefined)) { + yAxisText = gd._fullLayout.yaxis.title.text; + } + + + var c2mData = {}; + var labels = []; + var types = []; + var fullData = gd._fullData; + // TODO: We're looping through all traces, and that's fine, but it might be helpful to discern how things are organized + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + // TODO: what happens if this doesn't run, weird plotly errors + for(var codecI = 0; codecI < codecs.length; codecI++) { + var test = codecs[codecI].test(trace); + if(!test) continue; + var label = test.name ? test.name : i.toString() + " "; + + var labelCount = 0; + var originalLabel = label; + while(label in c2mData) { + labelCount++; + label = originalLabel + labelCount.toString(); + } + + labels.push(label); + types.push(test.type); + c2mData[label] = codecs[codecI].process(trace); + } + } // TODO add unsupported codec + + c2mContext.c2mHandler = c2m.c2mChart({ + title: titleText, + type: types, // needs to be generated + axes: { + x: { // needs to be generated + label: xAxisText + }, + y: { + label: yAxisText + }, + }, + element: gd, + cc: ccElement, + data: c2mData, + options: c2mContext.options, + info: c2mContext.info + }); + // TODO: We need to handle the possible error that c2mChart returns +} +exports.initC2M = initC2M; + +function initClosedCaptionDiv(gd, config) { + if(config.generate) { + var closedCaptions = document.createElement('div'); + closedCaptions.id = config.elId; + closedCaptions.className = config.elClassname; + gd.parentNode.insertBefore(closedCaptions, gd.nextSibling); // this really might not work + return closedCaptions; + } else { + return document.getElementById(config.elId); + } + return null; +} + +exports.initClosedCaptionDiv = initClosedCaptionDiv; diff --git a/src/accessibility/c2m/xy_scatter_codec.js b/src/accessibility/c2m/xy_scatter_codec.js new file mode 100644 index 00000000000..50d72d7a1c7 --- /dev/null +++ b/src/accessibility/c2m/xy_scatter_codec.js @@ -0,0 +1,26 @@ +// This codec serves for one x axis and one y axis + +function test(trace) { + if(!trace) return null; + if(!trace.type || trace.type !== 'scatter') return null; + // TODO: I think we can have an x OR a y + if( (trace.y === undefined || trace.y.length === 0) && (trace.x === undefined || trace.x.length === 0) ) return null; + return {type: 'scatter', name: trace.name}; +} +exports.test = test; + +function process(trace) { + var traceData = []; + var x = trace.x && trace.x.length !== 0 ? trace.x : []; + var y = trace.y && trace.y.length !== 0 ? trace.y : []; + + for(var p = 0; p < Math.max(x.length, y.length); p++) { + traceData.push({ // TODO I think we're copying the data here, bad. + x: x[p] ? x[p] : p, + y: y[p] ? y[p] : p, + label: trace.text[p] ? trace.text[p] : p + }); + } + return traceData; +} +exports.process = process; diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 8e3f90ae9fd..0b72ae29461 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,111 +1,20 @@ 'use strict'; var Lib = require('../lib'); -var c2m = require('chart2music'); -var Fx = require('../components/fx'); - -var supportedAccessibilityLibraries = ['chart2music']; +var c2mPlotly = require('../accessibility/c2m'); function enable(gd) { - var accessibilityVars = gd._context.accessibility; - var library = accessibilityVars.library; - var options = accessibilityVars.options; - var info = accessibilityVars.info; - var closedCaptionsOptions = accessibilityVars.closedCaptions; - if(!supportedAccessibilityLibraries.includes(library)) { - // 'Accessibility not implemented for library: ' + library - return; - } - if(library === 'chart2music') { - var c2mData = {}; - var labels = []; - var fullData = gd._fullData; - for(var i = 0; i < fullData.length; i++) { - var trace = fullData[i] ? fullData[i] : {}; - var type = trace.type; - var x = trace.x !== undefined ? trace.x : []; - var y = trace.y !== undefined ? trace.y : []; - var name = trace.name !== undefined ? trace.name : i; - var text = trace.text !== undefined ? trace.text : []; - if(type === 'scatter') { - var traceData = []; - if('y' in trace) { - for(var p = 0; p < y.length; p++) { - traceData.push( - { - x: x.length > 0 ? x[p] : p, - y: y[p], - label: text[p] ? text[p] : p - }); - } - if(traceData.length === 0) continue; - c2mData[name] = traceData; - labels.push(name); - } - } else { - // 'Accessibility not implemented for trace type: ' + trace.type - return; - } - } - var closedCaptions; - if(closedCaptionsOptions.generate) { - closedCaptions = document.createElement('div'); // should this be Lib.getGraphDiv()? - closedCaptions.id = closedCaptionsOptions.elId; - closedCaptions.className = closedCaptionsOptions.elClassname; - gd.parentNode.insertBefore(closedCaptions, gd.nextSibling); // this does get generated - // TODO we need a better generator - } else { - closedCaptions = document.getElementById(closedCaptionsOptions.elId); - if(closedCaptions === null) { - // TODO maybe handle this better for the developer? - return; - } - } - - var titleText = 'Chart'; - if((gd._fullLayout.title !== undefined) && (gd._fullLayout.title.text !== undefined)) { - titleText = gd._fullLayout.title.text; - } + // Collecting defaults + var defaultConfig = gd._context.accessibility; - var xAxisText = 'X Axis'; - if((gd._fullLayout.xaxis !== undefined) && - (gd._fullLayout.xaxis.title !== undefined) && - (gd._fullLayout.xaxis.title.text !== undefined)) { - xAxisText = gd._fullLayout.xaxis.title.text; - } - var yAxisText = 'Y Axis'; - if((gd._fullLayout.yaxis !== undefined) && - (gd._fullLayout.yaxis.title !== undefined) && - (gd._fullLayout.yaxis.title.text !== undefined)) { - yAxisText = gd._fullLayout.yaxis.title.text; - } - // Arguably should pass all config as copy to C2M - // If C2M eventually modifies them in any way (minus w/ _ prefix) - // It will always break transition/redraw logic in react - var options2 = Lib.extendDeepAll({}, options); - options2.onFocusCallback = function(dataInfo) { - Fx.hover(gd, [{ - curveNumber: labels.indexOf(dataInfo.slice), - pointNumber: dataInfo.index - }]); - }; - c2m.c2mChart({ - title: titleText, - type: 'line', - axes: { - x: { - label: xAxisText - }, - y: { - label: yAxisText - }, - }, - element: gd, - cc: closedCaptions, - data: c2mData, - options2: options, - info: info - }); + if(defaultConfig.library === 'chart2music') { + c2mPlotly.initC2M(gd, defaultConfig); + } else { + // User has some bunk configuration values, what do we do here? + return; } + } exports.enable = enable; + + From 4c7503570fdc290413c2c831cfd9a9a63b74ed7b Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 28 Feb 2024 07:29:29 -0500 Subject: [PATCH 41/68] Lint --- src/accessibility/c2m/all_codecs.js | 5 +- src/accessibility/c2m/index.js | 159 ++++++++++++---------- src/accessibility/c2m/xy_scatter_codec.js | 33 ++--- src/plot_api/accessibility.js | 10 +- 4 files changed, 107 insertions(+), 100 deletions(-) diff --git a/src/accessibility/c2m/all_codecs.js b/src/accessibility/c2m/all_codecs.js index ec7aea5faa6..540f4de3a3f 100644 --- a/src/accessibility/c2m/all_codecs.js +++ b/src/accessibility/c2m/all_codecs.js @@ -1,4 +1,5 @@ +'use strict'; -var xy_scatter = require('./xy_scatter_codec'); +var scatterXY = require('./xy_scatter_codec'); -exports.codecs = [ xy_scatter ]; +exports.codecs = [ scatterXY ]; diff --git a/src/accessibility/c2m/index.js b/src/accessibility/c2m/index.js index a27c21adbd4..ddc11b4cb12 100644 --- a/src/accessibility/c2m/index.js +++ b/src/accessibility/c2m/index.js @@ -1,5 +1,8 @@ +'use strict'; + var c2m = require('chart2music'); var Fx = require('../../components/fx'); +var Lib = require('../lib'); var codecs = require('./all_codecs').codecs; // var codecs = [ require('./xy_scatter_codec') ] @@ -8,93 +11,100 @@ var codecs = require('./all_codecs').codecs; // b) may break weak tree shakers who bail if `require()` not top-level function initC2M(gd, defaultConfig) { - // TODO: should this be Lib.getGraphDiv()? - // TODO what is there besides a fullReset? - // TODO Do we need the capacity to add data (live listen?) - // TODO Do we need the capacity to reset all data - var c2mContext = gd._context._c2m = {}; - c2mContext.options = Lib.extendDeepAll({}, defaultConfig.options); - c2mContext.info = Lib.extendDeepAll({}, defaultConfig.info); - c2mContext.ccOptions = Lib.extendDeepAll({}, defaultConfig.closedCaptions); + // TODO: should this be Lib.getGraphDiv()? + // TODO what is there besides a fullReset? + // TODO Do we need the capacity to add data (live listen?) + // TODO Do we need the capacity to reset all data + var c2mContext = gd._context._c2m = {}; + c2mContext.options = Lib.extendDeepAll({}, defaultConfig.options); + c2mContext.info = Lib.extendDeepAll({}, defaultConfig.info); + c2mContext.ccOptions = Lib.extendDeepAll({}, defaultConfig.closedCaptions); - c2mContext.options.onFocusCallback = function(dataInfo) { - Fx.hover(gd, [{ - curveNumber: labels.indexOf(dataInfo.slice), - pointNumber: dataInfo.index - }]); - }; + var labels = []; // TODO this probably needs to be stored in context- + // when data is updated this specific instance needs to be updated + // or the below function eneds to be reset to use the new instance of labels. + c2mContext.options.onFocusCallback = function(dataInfo) { + Fx.hover(gd, [{ + curveNumber: labels.indexOf(dataInfo.slice), + pointNumber: dataInfo.index + }]); + }; + // I generally don't like using closures like this. + // In this case it works out, we effectively treat 'labels' + // like a global, but it's a local. + // I'd rather the callback be given its c2m handler which + // could store extra data. Or bind c2mContext as `this`. - var titleText = 'Chart'; - if((gd._fullLayout.title !== undefined) && (gd._fullLayout.title.text !== undefined)) { - titleText = gd._fullLayout.title.text; - } + var titleText = 'Chart'; + if((gd._fullLayout.title !== undefined) && (gd._fullLayout.title.text !== undefined)) { + titleText = gd._fullLayout.title.text; + } - var ccElement = initClosedCaptionDiv(gd, c2mContext.ccOptions); + var ccElement = initClosedCaptionDiv(gd, c2mContext.ccOptions); - // TODO: - // I believe that title OR title.text could contain the information needed - // So we should stop if title is a type string - // Furthermore, I think that the traces would have to point to their axis, - // since it might not be x1, could be x2, etc - // So this really needs to be part of process() - var xAxisText = 'X Axis'; - if((gd._fullLayout.xaxis !== undefined) && + // TODO: + // I believe that title OR title.text could contain the information needed + // So we should stop if title is a type string + // Furthermore, I think that the traces would have to point to their axis, + // since it might not be x1, could be x2, etc + // So this really needs to be part of process() + var xAxisText = 'X Axis'; + if((gd._fullLayout.xaxis !== undefined) && (gd._fullLayout.xaxis.title !== undefined) && (gd._fullLayout.xaxis.title.text !== undefined)) { - xAxisText = gd._fullLayout.xaxis.title.text; - } - var yAxisText = 'Y Axis'; - if((gd._fullLayout.yaxis !== undefined) && + xAxisText = gd._fullLayout.xaxis.title.text; + } + var yAxisText = 'Y Axis'; + if((gd._fullLayout.yaxis !== undefined) && (gd._fullLayout.yaxis.title !== undefined) && (gd._fullLayout.yaxis.title.text !== undefined)) { - yAxisText = gd._fullLayout.yaxis.title.text; - } + yAxisText = gd._fullLayout.yaxis.title.text; + } - var c2mData = {}; - var labels = []; - var types = []; - var fullData = gd._fullData; - // TODO: We're looping through all traces, and that's fine, but it might be helpful to discern how things are organized - for(var i = 0; i < fullData.length; i++) { - var trace = fullData[i]; - // TODO: what happens if this doesn't run, weird plotly errors - for(var codecI = 0; codecI < codecs.length; codecI++) { - var test = codecs[codecI].test(trace); - if(!test) continue; - var label = test.name ? test.name : i.toString() + " "; + var c2mData = {}; + var types = []; + var fullData = gd._fullData; + // TODO: We're looping through all traces, and that's fine, but it might be helpful to discern how things are organized + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + // TODO: what happens if this doesn't run, weird c2m errors + for(var codecI = 0; codecI < codecs.length; codecI++) { + var test = codecs[codecI].test(trace); + if(!test) continue; + var label = test.name ? test.name : i.toString() + ' '; - var labelCount = 0; - var originalLabel = label; - while(label in c2mData) { - labelCount++; - label = originalLabel + labelCount.toString(); - } + var labelCount = 0; + var originalLabel = label; + while(label in c2mData) { + labelCount++; + label = originalLabel + labelCount.toString(); + } - labels.push(label); - types.push(test.type); - c2mData[label] = codecs[codecI].process(trace); - } - } // TODO add unsupported codec + labels.push(label); + types.push(test.type); + c2mData[label] = codecs[codecI].process(trace); + } + } // TODO add unsupported codec - c2mContext.c2mHandler = c2m.c2mChart({ - title: titleText, - type: types, // needs to be generated - axes: { - x: { // needs to be generated - label: xAxisText - }, - y: { - label: yAxisText - }, - }, - element: gd, - cc: ccElement, - data: c2mData, - options: c2mContext.options, - info: c2mContext.info - }); - // TODO: We need to handle the possible error that c2mChart returns + c2mContext.c2mHandler = c2m.c2mChart({ + title: titleText, + type: types, + axes: { + x: { // needs to be generated + label: xAxisText + }, + y: { + label: yAxisText + }, + }, + element: gd, + cc: ccElement, + data: c2mData, + options: c2mContext.options, + info: c2mContext.info + }); + // TODO: We need to handle the possible error that c2mChart returns } exports.initC2M = initC2M; @@ -108,7 +118,6 @@ function initClosedCaptionDiv(gd, config) { } else { return document.getElementById(config.elId); } - return null; } exports.initClosedCaptionDiv = initClosedCaptionDiv; diff --git a/src/accessibility/c2m/xy_scatter_codec.js b/src/accessibility/c2m/xy_scatter_codec.js index 50d72d7a1c7..88ad11c73ec 100644 --- a/src/accessibility/c2m/xy_scatter_codec.js +++ b/src/accessibility/c2m/xy_scatter_codec.js @@ -1,26 +1,27 @@ +'use strict'; // This codec serves for one x axis and one y axis function test(trace) { - if(!trace) return null; - if(!trace.type || trace.type !== 'scatter') return null; - // TODO: I think we can have an x OR a y - if( (trace.y === undefined || trace.y.length === 0) && (trace.x === undefined || trace.x.length === 0) ) return null; - return {type: 'scatter', name: trace.name}; + if(!trace) return null; + if(!trace.type || trace.type !== 'scatter') return null; + // TODO: I think we can have an x OR a y + if((trace.y === undefined || trace.y.length === 0) && (trace.x === undefined || trace.x.length === 0)) return null; + return {type: 'scatter', name: trace.name}; } exports.test = test; function process(trace) { - var traceData = []; - var x = trace.x && trace.x.length !== 0 ? trace.x : []; - var y = trace.y && trace.y.length !== 0 ? trace.y : []; + var traceData = []; + var x = trace.x && trace.x.length !== 0 ? trace.x : []; + var y = trace.y && trace.y.length !== 0 ? trace.y : []; - for(var p = 0; p < Math.max(x.length, y.length); p++) { - traceData.push({ // TODO I think we're copying the data here, bad. - x: x[p] ? x[p] : p, - y: y[p] ? y[p] : p, - label: trace.text[p] ? trace.text[p] : p - }); - } - return traceData; + for(var p = 0; p < Math.max(x.length, y.length); p++) { + traceData.push({ // TODO I think we're copying the data here, bad. + x: x[p] ? x[p] : p, + y: y[p] ? y[p] : p, + label: trace.text[p] ? trace.text[p] : p + }); + } + return traceData; } exports.process = process; diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 0b72ae29461..6c815305871 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,6 +1,5 @@ 'use strict'; -var Lib = require('../lib'); var c2mPlotly = require('../accessibility/c2m'); function enable(gd) { @@ -8,13 +7,10 @@ function enable(gd) { var defaultConfig = gd._context.accessibility; if(defaultConfig.library === 'chart2music') { - c2mPlotly.initC2M(gd, defaultConfig); + c2mPlotly.initC2M(gd, defaultConfig); } else { - // User has some bunk configuration values, what do we do here? - return; + // User has some bunk configuration values, what do we do here? + return; } - } exports.enable = enable; - - From 3c49ed6ffd523919ba300a632c8382c3ad8619d7 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 28 Feb 2024 10:22:09 -0500 Subject: [PATCH 42/68] Fix import path --- src/accessibility/c2m/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accessibility/c2m/index.js b/src/accessibility/c2m/index.js index ddc11b4cb12..186d5780be6 100644 --- a/src/accessibility/c2m/index.js +++ b/src/accessibility/c2m/index.js @@ -2,7 +2,7 @@ var c2m = require('chart2music'); var Fx = require('../../components/fx'); -var Lib = require('../lib'); +var Lib = require('../../lib'); var codecs = require('./all_codecs').codecs; // var codecs = [ require('./xy_scatter_codec') ] From ed189076b68de9d31c32bdd7d0fb6512108ae2bf Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Mon, 23 Sep 2024 20:08:08 +0200 Subject: [PATCH 43/68] Change name of config to sonification, clean up directory --- devtools/test_dashboard/index-mathjax3.html | 4 ---- devtools/test_dashboard/index-mathjax3chtml.html | 4 ---- devtools/test_dashboard/index-strict.html | 4 ---- devtools/test_dashboard/index.html | 5 +---- src/accessibility/README.md | 2 +- .../{c2m => sonification}/README.md | 4 ++-- .../{c2m => sonification}/all_codecs.js | 0 src/accessibility/{c2m => sonification}/index.js | 4 ---- .../{c2m => sonification}/xy_scatter_codec.js | 0 src/plot_api/accessibility.js | 16 +++++----------- src/plot_api/plot_api.js | 2 +- src/plot_api/plot_config.js | 5 ++--- test/image/mocks/line_scatter.json | 11 ++++++++++- 13 files changed, 22 insertions(+), 39 deletions(-) rename src/accessibility/{c2m => sonification}/README.md (89%) rename src/accessibility/{c2m => sonification}/all_codecs.js (100%) rename src/accessibility/{c2m => sonification}/index.js (95%) rename src/accessibility/{c2m => sonification}/xy_scatter_codec.js (100%) diff --git a/devtools/test_dashboard/index-mathjax3.html b/devtools/test_dashboard/index-mathjax3.html index 720633e5ca7..c2365f1bd12 100644 --- a/devtools/test_dashboard/index-mathjax3.html +++ b/devtools/test_dashboard/index-mathjax3.html @@ -24,10 +24,6 @@

no MathJax: Apple: $2, Orange: $3

-
-
Closed Captions
-
Test
-
diff --git a/devtools/test_dashboard/index.html b/devtools/test_dashboard/index.html index 54a7f844f70..3aa41a0b346 100644 --- a/devtools/test_dashboard/index.html +++ b/devtools/test_dashboard/index.html @@ -20,10 +20,7 @@
-
-
Closed Captions
-
Test
-
+ diff --git a/src/accessibility/README.md b/src/accessibility/README.md index 5db9ba55fa3..c4cb77b411a 100644 --- a/src/accessibility/README.md +++ b/src/accessibility/README.md @@ -1,3 +1,3 @@ # accesibility -While anticipating more accesibility libraries in the future, as of today this is a trivial wrapper over the `c2m` directory. +While anticipating more accessibility libraries in the future, as of today this is a trivial wrapper over the `c2m` directory. diff --git a/src/accessibility/c2m/README.md b/src/accessibility/sonification/README.md similarity index 89% rename from src/accessibility/c2m/README.md rename to src/accessibility/sonification/README.md index d704ff32ef1..efa0dd20d47 100644 --- a/src/accessibility/c2m/README.md +++ b/src/accessibility/sonification/README.md @@ -1,4 +1,4 @@ -# `plotly.js` wrapper for `c2m`, sonified charts +# `plotly.js` wrapper for `chart2music`, sonified charts This wrapper attaches a context object to `gd._context._c2m` with the following properties: @@ -15,7 +15,7 @@ The first three have most of their values set by the defaults in *src/plot_api/p **index.js** exposes the following api: * `initC2M(gd, defaultConfig)` full resets the `c2mChart` object. - * `defaultConfig` is equal to `gd._context.accessibility`, as defined in *src/plot_api/plot_config.js*. + * `defaultConfig` is equal to `gd._context.sonification`, as defined in *src/plot_api/plot_config.js*. * `initClosedCaptionDiv(gd, config)` finds or creates the closed caption div, depending on `config`. diff --git a/src/accessibility/c2m/all_codecs.js b/src/accessibility/sonification/all_codecs.js similarity index 100% rename from src/accessibility/c2m/all_codecs.js rename to src/accessibility/sonification/all_codecs.js diff --git a/src/accessibility/c2m/index.js b/src/accessibility/sonification/index.js similarity index 95% rename from src/accessibility/c2m/index.js rename to src/accessibility/sonification/index.js index 186d5780be6..77fcbe4e624 100644 --- a/src/accessibility/c2m/index.js +++ b/src/accessibility/sonification/index.js @@ -5,10 +5,6 @@ var Fx = require('../../components/fx'); var Lib = require('../../lib'); var codecs = require('./all_codecs').codecs; -// var codecs = [ require('./xy_scatter_codec') ] -// I intentionally don't do that because -// a) it's not possible in ESM w/ `import {}` -// b) may break weak tree shakers who bail if `require()` not top-level function initC2M(gd, defaultConfig) { // TODO: should this be Lib.getGraphDiv()? diff --git a/src/accessibility/c2m/xy_scatter_codec.js b/src/accessibility/sonification/xy_scatter_codec.js similarity index 100% rename from src/accessibility/c2m/xy_scatter_codec.js rename to src/accessibility/sonification/xy_scatter_codec.js diff --git a/src/plot_api/accessibility.js b/src/plot_api/accessibility.js index 6c815305871..1e3af27da74 100644 --- a/src/plot_api/accessibility.js +++ b/src/plot_api/accessibility.js @@ -1,16 +1,10 @@ 'use strict'; -var c2mPlotly = require('../accessibility/c2m'); +var c2mPlotly = require('../accessibility/sonification'); -function enable(gd) { +function enable_sonification(gd) { // Collecting defaults - var defaultConfig = gd._context.accessibility; - - if(defaultConfig.library === 'chart2music') { - c2mPlotly.initC2M(gd, defaultConfig); - } else { - // User has some bunk configuration values, what do we do here? - return; - } + var defaultConfig = gd._context.sonification; + c2mPlotly.initC2M(gd, defaultConfig); } -exports.enable = enable; +exports.enable_sonification = enable_sonification; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index c8f5894b98b..aba95ef9ad3 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -392,7 +392,7 @@ function _doPlot(gd, data, layout, config) { } seq.push(saveRangeInitialForInsideTickLabels); - if(gd._context.accessibility.enabled) seq.push(accessibility.enable); + if(gd._context.sonification.enabled) seq.push(accessibility.enable_sonification); seq.push(Plots.previousPromises); diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 4184404ea3f..0c8887fea86 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -473,11 +473,10 @@ var configAttributes = { ].join(' ') }, - accessibility: { + sonification: { valType: 'any', dflt: { - library: 'chart2music', - enabled: true, + enabled: false, options: {}, info: {}, closedCaptions: {generate: false, elId: 'c2m-plotly-cc', elClassname: 'c2m-plotly-closed_captions'} diff --git a/test/image/mocks/line_scatter.json b/test/image/mocks/line_scatter.json index 5a38f3ab590..0c588fe11a1 100644 --- a/test/image/mocks/line_scatter.json +++ b/test/image/mocks/line_scatter.json @@ -33,5 +33,14 @@ "line": { "width": 10, "color": "red" }, "marker": { "size": 20, "color": "blue" } } - ] + ], + "config": { + "sonification": { + "enabled": true, + "closedCaptions": { + "generate": false, + "elId": "c2m-plotly-cc" + } + } + } } From 1e4bcefd54ae0e5e4e0db0c18841d6ec4db5a653 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Mon, 23 Sep 2024 20:08:55 +0200 Subject: [PATCH 44/68] Spelling --- src/accessibility/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accessibility/README.md b/src/accessibility/README.md index c4cb77b411a..b4929961240 100644 --- a/src/accessibility/README.md +++ b/src/accessibility/README.md @@ -1,3 +1,3 @@ -# accesibility +# accessibility While anticipating more accessibility libraries in the future, as of today this is a trivial wrapper over the `c2m` directory. From 2c1abf4440f995b7b04b3a8fcc8be1c4e50c6f88 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Mon, 23 Sep 2024 20:09:20 +0200 Subject: [PATCH 45/68] fix directory name --- src/accessibility/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accessibility/README.md b/src/accessibility/README.md index b4929961240..f58a32e3e6a 100644 --- a/src/accessibility/README.md +++ b/src/accessibility/README.md @@ -1,3 +1,3 @@ # accessibility -While anticipating more accessibility libraries in the future, as of today this is a trivial wrapper over the `c2m` directory. +While anticipating more accessibility libraries in the future, as of today this is a trivial wrapper over the `sonification` directory. From 614fb8b0136053535b5ae7dbfcb17e6c20633a71 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Mon, 23 Sep 2024 20:23:59 +0200 Subject: [PATCH 46/68] Reset package-lock --- package-lock.json | 108 +--------------------------------------------- 1 file changed, 1 insertion(+), 107 deletions(-) diff --git a/package-lock.json b/package-lock.json index cee9a6d891b..3e9f4bc2407 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "@turf/centroid": "^7.1.0", "base64-arraybuffer": "^1.0.2", "canvas-fit": "^1.5.0", - "chart2music": "^1.13.0", "color-alpha": "1.0.4", "color-normalize": "1.5.0", "color-parse": "2.0.0", @@ -754,92 +753,6 @@ "node": ">=18" } }, - "node_modules/@formatjs/ecma402-abstract": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz", - "integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==", - "dependencies": { - "@formatjs/intl-localematcher": "0.5.4", - "tslib": "^2.4.0" - } - }, - "node_modules/@formatjs/fast-memoize": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", - "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.6.tgz", - "integrity": "sha512-etVau26po9+eewJKYoiBKP6743I1br0/Ie00Pb/S/PtmYfmjTcOn2YCh2yNkSZI12h6Rg+BOgQYborXk46BvkA==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.18.2", - "@formatjs/icu-skeleton-parser": "1.8.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.0.tgz", - "integrity": "sha512-QWLAYvM0n8hv7Nq5BEs4LKIjevpVpbGLAJgOaYzg9wABEoX1j0JO1q2/jVkO6CVlq0dbsxZCngS5aXbysYueqA==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.18.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@formatjs/intl": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.2.tgz", - "integrity": "sha512-raPGWr3JRv3neXV78SqPFrGC05fIbhhNzVghHNxFde27ls2KkXiMhtP7HBybjGpikVSjjhdhaZto+4p1vmm9bQ==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.18.2", - "@formatjs/fast-memoize": "2.2.0", - "@formatjs/icu-messageformat-parser": "2.7.6", - "@formatjs/intl-displaynames": "6.6.6", - "@formatjs/intl-listformat": "7.5.5", - "intl-messageformat": "10.5.12", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "typescript": "^4.7 || 5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@formatjs/intl-displaynames": { - "version": "6.6.6", - "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.6.tgz", - "integrity": "sha512-Dg5URSjx0uzF8VZXtHb6KYZ6LFEEhCbAbKoYChYHEOnMFTw/ZU3jIo/NrujzQD2EfKPgQzIq73LOUvW6Z/LpFA==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.18.2", - "@formatjs/intl-localematcher": "0.5.4", - "tslib": "^2.4.0" - } - }, - "node_modules/@formatjs/intl-listformat": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.5.tgz", - "integrity": "sha512-XoI52qrU6aBGJC9KJddqnacuBbPlb/bXFN+lIFVFhQ1RnFHpzuFrlFdjD9am2O7ZSYsyqzYRpkVcXeT1GHkwDQ==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.18.2", - "@formatjs/intl-localematcher": "0.5.4", - "tslib": "^2.4.0" - } - }, - "node_modules/@formatjs/intl-localematcher": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", - "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2483,14 +2396,6 @@ "node": ">=4.0.0" } }, - "node_modules/chart2music": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/chart2music/-/chart2music-1.17.0.tgz", - "integrity": "sha512-oDlISz51Mttx74cbA8REJDHxennlRxdafSSyimeqtsk/EUF3wO+KCuBMEGj9ZYTemlfe+GtfUa9kkV6tPScEvQ==", - "dependencies": { - "@formatjs/intl": "2.10.2" - } - }, "node_modules/check-node-version": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-4.2.1.tgz", @@ -5633,17 +5538,6 @@ "node": ">= 0.4" } }, - "node_modules/intl-messageformat": { - "version": "10.5.12", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.12.tgz", - "integrity": "sha512-izl0uxhy/melhw8gP2r8pGiVieviZmM4v5Oqx3c1/R7g9cwER2smmGfSjcIsp8Y3Q53bfciL/gkxacJRx/dUvg==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.18.2", - "@formatjs/fast-memoize": "2.2.0", - "@formatjs/icu-messageformat-parser": "2.7.6", - "tslib": "^2.4.0" - } - }, "node_modules/into-stream": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", @@ -10180,7 +10074,7 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "devOptional": true, + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From 5f5e5998d9f874c505925fb25c6d7ec333b1edf6 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Mon, 23 Sep 2024 20:24:47 +0200 Subject: [PATCH 47/68] Update package-lock.json --- package-lock.json | 108 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 3e9f4bc2407..cee9a6d891b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@turf/centroid": "^7.1.0", "base64-arraybuffer": "^1.0.2", "canvas-fit": "^1.5.0", + "chart2music": "^1.13.0", "color-alpha": "1.0.4", "color-normalize": "1.5.0", "color-parse": "2.0.0", @@ -753,6 +754,92 @@ "node": ">=18" } }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz", + "integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.6.tgz", + "integrity": "sha512-etVau26po9+eewJKYoiBKP6743I1br0/Ie00Pb/S/PtmYfmjTcOn2YCh2yNkSZI12h6Rg+BOgQYborXk46BvkA==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/icu-skeleton-parser": "1.8.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.0.tgz", + "integrity": "sha512-QWLAYvM0n8hv7Nq5BEs4LKIjevpVpbGLAJgOaYzg9wABEoX1j0JO1q2/jVkO6CVlq0dbsxZCngS5aXbysYueqA==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.2.tgz", + "integrity": "sha512-raPGWr3JRv3neXV78SqPFrGC05fIbhhNzVghHNxFde27ls2KkXiMhtP7HBybjGpikVSjjhdhaZto+4p1vmm9bQ==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.6", + "@formatjs/intl-displaynames": "6.6.6", + "@formatjs/intl-listformat": "7.5.5", + "intl-messageformat": "10.5.12", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "typescript": "^4.7 || 5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@formatjs/intl-displaynames": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.6.tgz", + "integrity": "sha512-Dg5URSjx0uzF8VZXtHb6KYZ6LFEEhCbAbKoYChYHEOnMFTw/ZU3jIo/NrujzQD2EfKPgQzIq73LOUvW6Z/LpFA==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-listformat": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.5.tgz", + "integrity": "sha512-XoI52qrU6aBGJC9KJddqnacuBbPlb/bXFN+lIFVFhQ1RnFHpzuFrlFdjD9am2O7ZSYsyqzYRpkVcXeT1GHkwDQ==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2396,6 +2483,14 @@ "node": ">=4.0.0" } }, + "node_modules/chart2music": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/chart2music/-/chart2music-1.17.0.tgz", + "integrity": "sha512-oDlISz51Mttx74cbA8REJDHxennlRxdafSSyimeqtsk/EUF3wO+KCuBMEGj9ZYTemlfe+GtfUa9kkV6tPScEvQ==", + "dependencies": { + "@formatjs/intl": "2.10.2" + } + }, "node_modules/check-node-version": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-4.2.1.tgz", @@ -5538,6 +5633,17 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "10.5.12", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.12.tgz", + "integrity": "sha512-izl0uxhy/melhw8gP2r8pGiVieviZmM4v5Oqx3c1/R7g9cwER2smmGfSjcIsp8Y3Q53bfciL/gkxacJRx/dUvg==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.6", + "tslib": "^2.4.0" + } + }, "node_modules/into-stream": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", @@ -10074,7 +10180,7 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From fe9736e13d303c6981a8be762927bd70bfd985f9 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Mon, 23 Sep 2024 20:30:01 +0200 Subject: [PATCH 48/68] Reset tests --- test/jasmine/tests/transition_test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/jasmine/tests/transition_test.js b/test/jasmine/tests/transition_test.js index 83ec43edc7d..ea42d3192b1 100644 --- a/test/jasmine/tests/transition_test.js +++ b/test/jasmine/tests/transition_test.js @@ -558,10 +558,11 @@ describe('Plotly.react transitions:', function() { var data = [{y: [1, 2, 1]}]; var layout = {transition: {duration: 10}}; var config = {scrollZoom: true}; + Plotly.react(gd, data, layout, config) .then(function() { assertSpies('first draw', [ - [Plots, 'transitionFromReact', 0] // so Plots.transitionFromReact should equal 0, ie didn't get called + [Plots, 'transitionFromReact', 0] ]); }) .then(function() { @@ -580,7 +581,7 @@ describe('Plotly.react transitions:', function() { }) .then(function() { assertSpies('no config change', [ - [Plots, 'transitionFromReact', 1] // react is not calling transitionFromReact, why? + [Plots, 'transitionFromReact', 1] ]); }) .then(done, done.fail); From 59ad5c6ffa303df6d54a1ce867ae49f18edb9a1a Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Mon, 23 Sep 2024 21:28:34 +0200 Subject: [PATCH 49/68] Update auto-generated schema --- src/plot_api/plot_config.js | 2 +- test/plot-schema.json | 29 ++++++++++++++--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 0c8887fea86..4624a7b2306 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -481,7 +481,7 @@ var configAttributes = { info: {}, closedCaptions: {generate: false, elId: 'c2m-plotly-cc', elClassname: 'c2m-plotly-closed_captions'} }, - description: ['Accessibility options: which library to use; whether to enable, options to pass to the library, info to pass to the library, closedCaptions to control how plotly renders the closed-captions element.', + description: ['Sonification options: whether to enable, options to pass to the library, info to pass to the library, closedCaptions to control how plotly renders the closed-captions element.', 'chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ' ].join(' ') }, diff --git a/test/plot-schema.json b/test/plot-schema.json index 56e01387d40..e17f5685239 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -104,21 +104,6 @@ } }, "config": { - "accessibility": { - "description": "Accessibility options: which library to use; whether to enable, options to pass to the library, info to pass to the library, closedCaptions to control how plotly renders the closed-captions element. chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ", - "dflt": { - "closedCaptions": { - "elClassname": "c2m-plotly-closed_captions", - "elId": "c2m-plotly-cc", - "generate": false - }, - "enabled": true, - "info": {}, - "library": "chart2music", - "options": {} - }, - "valType": "any" - }, "autosizable": { "description": "Determines whether the graphs are plotted with respect to layout.autosize:true and infer its container size.", "dflt": false, @@ -370,6 +355,20 @@ "dflt": true, "valType": "boolean" }, + "sonification": { + "description": "Accessibility options: which library to use; whether to enable, options to pass to the library, info to pass to the library, closedCaptions to control how plotly renders the closed-captions element. chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ", + "dflt": { + "closedCaptions": { + "elClassname": "c2m-plotly-closed_captions", + "elId": "c2m-plotly-cc", + "generate": false + }, + "enabled": false, + "info": {}, + "options": {} + }, + "valType": "any" + }, "staticPlot": { "description": "Determines whether the graphs are interactive or not. If *false*, no interactivity, for export or image generation.", "dflt": false, From d18c584ec0d208c73d0919f2b7c8780a0e51cd74 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 24 Sep 2024 15:17:49 +0200 Subject: [PATCH 50/68] Rename more stuff to sonification and clean up contributing --- CONTRIBUTING.md | 4 +- .../sonification/enable_sonification.js} | 0 src/plot_api/plot_api.js | 4 +- test/image/mocks/zz-chart2music.json | 58 +++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) rename src/{plot_api/accessibility.js => accessibility/sonification/enable_sonification.js} (100%) create mode 100644 test/image/mocks/zz-chart2music.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f7cc0f1537..f7db617ad9d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -276,9 +276,9 @@ npm run baseline mock_* ``` **IMPORTANT:** the `baseline`, `test-image` and `test-export` scripts do **not** bundle the source files before -running the image tests. We recommend running `npm run watch` or `npm start` in +running the image tests. We recommend running `npm start` in a separate tab to ensure that the most up-to-date code is used. -Also if you are adding a new mock, you may need to re-run `npm start` or `npm run watch` +Also if you are adding a new mock, you may need to re-run `npm start` to be able to find the new mock in the browser. To help ensure valid attributes are used in your new mock(s), please run `npm run test-mock` or `npm run test-mock mock_name(s)` after adding new mocks or implementing any new attributes. diff --git a/src/plot_api/accessibility.js b/src/accessibility/sonification/enable_sonification.js similarity index 100% rename from src/plot_api/accessibility.js rename to src/accessibility/sonification/enable_sonification.js diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index aba95ef9ad3..c33d333a2de 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -29,7 +29,7 @@ var manageArrays = require('./manage_arrays'); var helpers = require('./helpers'); var subroutines = require('./subroutines'); var editTypes = require('./edit_types'); -var accessibility = require('./accessibility'); +var sonification = require('../accessibility/sonification/enable_sonification'); var AX_NAME_PATTERN = require('../plots/cartesian/constants').AX_NAME_PATTERN; @@ -392,7 +392,7 @@ function _doPlot(gd, data, layout, config) { } seq.push(saveRangeInitialForInsideTickLabels); - if(gd._context.sonification.enabled) seq.push(accessibility.enable_sonification); + if(gd._context.sonification.enabled) seq.push(sonification.enable_sonification); seq.push(Plots.previousPromises); diff --git a/test/image/mocks/zz-chart2music.json b/test/image/mocks/zz-chart2music.json new file mode 100644 index 00000000000..69f941dc1ed --- /dev/null +++ b/test/image/mocks/zz-chart2music.json @@ -0,0 +1,58 @@ +{ + "data": [ + { + "x": [1, 2, 3, 4], + "y": [10, 15, 13, 17], + "mode": "markers", + "type": "scatter", + "marker": { + "opacity": 0.5 + } + }, + { + "x": [2, 3, 4, 5], + "y": [16, 5, 11, 9], + "mode": "lines", + "type": "scatter", + "opacity": 0.5 + }, + { + "x": [1, 2, 3, 4], + "y": [12, 9, 15, 12], + "mode": "lines+markers", + "type": "scatter", + "opacity": 0.707, + "marker": { + "opacity": 0.707 + } + }, + { + "x": [1, 2, 3], + "y": [1, 2, 3], + "opacity": 0.2, + "line": { "width": 10, "color": "red" }, + "marker": { "size": 20, "color": "blue" } + } + ], + "config": { + "sonification": { + "enabled": true, + "info": { + "notes": [ + "Sample notes to show" + ], + "annotations": [ + { + "x": 6, + "label": "This is an annotation to show at x=6" + } + ] + }, + "closedCaptions": { + "generate": false, + "elId": "c2m-plotly-cc" + } + } + } + } + \ No newline at end of file From 043d408290288cdd315b3eb8a74ddfb97c749f3a Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 24 Sep 2024 15:19:58 +0200 Subject: [PATCH 51/68] Update filepath for new location of enable_sonification.js --- src/accessibility/sonification/enable_sonification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/accessibility/sonification/enable_sonification.js b/src/accessibility/sonification/enable_sonification.js index 1e3af27da74..22ebcc4ced2 100644 --- a/src/accessibility/sonification/enable_sonification.js +++ b/src/accessibility/sonification/enable_sonification.js @@ -1,6 +1,6 @@ 'use strict'; -var c2mPlotly = require('../accessibility/sonification'); +var c2mPlotly = require('.'); function enable_sonification(gd) { // Collecting defaults From 129eb3b57779d2a01aa3976a92e3884356242a12 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 24 Sep 2024 21:28:33 +0200 Subject: [PATCH 52/68] Code readability improvements --- src/accessibility/sonification/index.js | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/accessibility/sonification/index.js b/src/accessibility/sonification/index.js index 77fcbe4e624..e5d3d80b78f 100644 --- a/src/accessibility/sonification/index.js +++ b/src/accessibility/sonification/index.js @@ -31,11 +31,7 @@ function initC2M(gd, defaultConfig) { // I'd rather the callback be given its c2m handler which // could store extra data. Or bind c2mContext as `this`. - var titleText = 'Chart'; - if((gd._fullLayout.title !== undefined) && (gd._fullLayout.title.text !== undefined)) { - titleText = gd._fullLayout.title.text; - } - + var titleText = gd._fullLayout.title?.text ?? 'Chart'; var ccElement = initClosedCaptionDiv(gd, c2mContext.ccOptions); // TODO: @@ -44,19 +40,8 @@ function initC2M(gd, defaultConfig) { // Furthermore, I think that the traces would have to point to their axis, // since it might not be x1, could be x2, etc // So this really needs to be part of process() - var xAxisText = 'X Axis'; - if((gd._fullLayout.xaxis !== undefined) && - (gd._fullLayout.xaxis.title !== undefined) && - (gd._fullLayout.xaxis.title.text !== undefined)) { - xAxisText = gd._fullLayout.xaxis.title.text; - } - var yAxisText = 'Y Axis'; - if((gd._fullLayout.yaxis !== undefined) && - (gd._fullLayout.yaxis.title !== undefined) && - (gd._fullLayout.yaxis.title.text !== undefined)) { - yAxisText = gd._fullLayout.yaxis.title.text; - } - + var xAxisText = gd._fullLayout.xaxis?.title?.text ?? 'X Axis'; + var yAxisText = gd._fullLayout.yaxis?.title?.text ?? 'Y Axis'; var c2mData = {}; var types = []; @@ -67,12 +52,12 @@ function initC2M(gd, defaultConfig) { // TODO: what happens if this doesn't run, weird c2m errors for(var codecI = 0; codecI < codecs.length; codecI++) { var test = codecs[codecI].test(trace); - if(!test) continue; + if (!test) continue; var label = test.name ? test.name : i.toString() + ' '; var labelCount = 0; var originalLabel = label; - while(label in c2mData) { + while (label in c2mData) { labelCount++; label = originalLabel + labelCount.toString(); } From acc85171c136264344278a224203d97fe0c86ed5 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 24 Sep 2024 22:06:02 +0200 Subject: [PATCH 53/68] Add baseline for new mock --- test/image/baselines/zz-chart2music.png | Bin 0 -> 32693 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/image/baselines/zz-chart2music.png diff --git a/test/image/baselines/zz-chart2music.png b/test/image/baselines/zz-chart2music.png new file mode 100644 index 0000000000000000000000000000000000000000..b91ffc35c956cd93d44cee811fb47331d83fd124 GIT binary patch literal 32693 zcmeFYXEdDc`ZuhO5?w^E(M3yw=tL*FA<;ub8J*EhUWtCZ7@40E{c$6Bvf2fe%8&-=b4YE* zgQGFSVjsqa$|34zooDqzm zP??pL6fQ0k*&d4dg&tqV7fGOh z7Xz6j9L+&v%{W2i#lcw^8Hf?o3yil>pY{q9k*<2XY{2&#CR0&v8Wo^V-9ck7vP!u4-X2Poc%@f!)S?j`T3kiewNLUdx{Ma&RHlm z;=1}wgh%X14-xe5A}4jJGIHEEhvrzFcRdak#HS)yC0l#BTPNr|FY`O`Nr!N9BMtKU zWQvrLF?#NL*OQZvuRYasHaz`$+qz~o3F0R-u`30tuQDI$!R0V)I4bR--L#`rY1q~> z_*c<2P*r|wLwM6z`x3j(!NJ{>n~yqXMI?b(>I|)4>hci_3rkFV{Kwe_-=!ZN46PB= zPn@S5s$Zkv;PD1u5eEMw?fSFD&*Mla4-GACY*Nyv#3$Akn`+$(9i5u5cpREO%UTYWS6nCyten?XXwTTd-Jq85-_h*okU18Mc^R5=fK^b_7H3>b9VmqCLn~1i>u}ObS^q2h3-YV zl*vjj5xv;`Z)O3tWszSdYw!`cc ztZOSP3&3)=9FLhrzk7%AJ6)Qw?9+6i0YAl44i1h&nUf>-;L4))o4wl`ex&Nxwu$!1cxJ$qL(=r6_Ud zoyxJQ!?jT(C-8ZT$edsKg=ejq>&LmkTYq^)#i}}w){lf|sFHvW)w4eLfCbs~EE9W# zzVEx&uy*>4dVlhrmh{6Eo8#d&qUiiW`uoS_vL=5Qwyj+hrkTD~L8l`l@8EFm->KpT zD=6S7kWEZX#JGp$rTO5isHntD8@+1S8P3xbZ+Y|3*xmgo9Ia~So2K;j)5lMqOqono z{k)unloaUU+8Qsc9L{l}YgDANxUOK2pkQUt&8N_v`>{Oy<7S4z7wvD4dNkZ}Z`}=v zF*H{9tFY@aK?GXp4n}blv$~kGY-;iIT^#B5)fe1j%FR>d)|xP1-#P!&g-<*P8=Rew zw2oTdjb%equOK}OumHtqhGM?kUZ;R;{kX)|De4B28%b%I-uvl)b-Dy0cq{DKE^ctJ z+pwalishjwgF$p>71X-nRokzfOfLk5LUAjd9tC+G zZ=&E530P@m3omrY9`KtMVVJ%;fCULZdh}uiv!`7|MK&e_x-pdNbvomDaJCd}IbQmj zlbib|ty6t4u=Lff%>8MSM#WTWQ6xk5vPH&_^bZZfE2t5`q+y@GRoQM^NlB zNr{<=B)r8u@xY5Lv|KQujh&qiddUb0Oh;!}arMkLu{tj38tc#D;XyMd@%;K=wp~b4 z4ykgp@1|yw8K;t7Y>M!CdbvAP3U!77%AtYM|z@7IK&KkO`6Ke&2IgYdG9Q1 z-=67|uO2Me%*gMUV1wG1}x z*wM=skLT2yv@ykm=3%!E9SEOtWtXR7q?|lFx)k^Opq+BIwp>43TVvDGW;3^3&EJ1z zivus}8o9ZZtE$KJ+5q^IN@S$(nQQU>YJ8&RCxk;?-Cj^&NFFzNCIcaiiE5*RqJU!h z>{fqQR!&!~E=Yt+nCuDvrh_i=Q#lOQ`Fj9XHEIoCF$4tJ@x?^%Y8x=N*apx-QN~RC zGe)&rxWc>AqkQwJ1LE{+rw&bu6O06k zK&fK>M@l!D_#+}hmT&rB5cH~?^fT?Tqm%6?1JKk&x@jH6m~mli5vnwTBKmYOk%9QT zsF1}t0U!>-sOIp&qn-RRIFhI*X=dZV&V|BrHHzv2<*XniKVYd z4p)2SEG-}QN#FW`>P-jl4Sp6KOyZJ&<|5vb3ApL`ne~pKa8JIv0}d7s)iLcBHo`DkzXuP{2;}UZrkp zZ+|>5wDH=_?oo+E_TK~>#WaKF?ma&@k4YcpbfjsK*tsuFLzrh42%aIJ4L3QL%I)P zFjFTQ1Fwcmip8-%m?OV&G7w0zg%5?KGkX?H+_S%BKhEc$H70S1L4|Y;h82&0-s3k1 zkk;rk4?`|#5RF#A7t?xjFEcZ799dgA8s^FhLpTiD)_vdAg zFW=@TpH}WE2&>0BePP`XpAk zGc=@AIhh0i#extF27^CJEFw#ve1p;#Myl{R3Fz_ykG~nmnq%UaoQKT6$H{_>w;YFY zU15c(WA`OvzgK!TNZ|mAL7N*x`$=SOFd`IGIzfTwB?CwXoS113JPyNZuGPdQ4)>W8 zF)b)==OxjA^%rZALv9DWeSefbO%q)Nur;5eOw*7rvk^#6S!v~FmODYj5qu|z(i*8<_EaWRi&}_bHiq^OosO-=-*sA*KSbQ5bn;!aEb`|OVbrZw=5Ibm zL>^(vZ^u`j|Aci2m z0tgyY`8~4zcK{sno7b-BXjC|AcZ*B+!y#P{%-1gURa@Ur1&7(}5gcx2kj-Dx=v_rY zHB9K`)LKF0`NhTHyhbZ)>nV>MXIef`eV)WKSDLUC9EY*m-WDHr(LvDX`ts{EU4yO_ zSjudQU~>>p3iSP`!`+JOr%Y01BPNw)mHK4O2eTB`XB)&3$W3^L8=xBMMq+;rEw@#x z@i*K*o%Jno2oRjVevo?wcIn}*HgAuhP7rpO#A{ZIf23`#nm#-0yC(_s^GWvisUM#W zPO9iK8m*0h;X=wNP{FLgTTslj31i1bmng75gbvX&5pOqXBDBFH2IgkJq((%>vJG7M z-~6(kSZxQ+Ze%bfjzwRJXw}3KY5-tVp#{2mEd2b$g$5<eL%+%V-_E+Uy-G$$k=gdffM|&+54Guk^1h2IZDR zO@0*DIKeG*uJs0xUi;hS{eYOmu(h>apQ#gJ@ZA=y-1q#yYB~74UeY5fzW%4TH zu==*}J=yVjwd0J$pXoL_#|c7^_zO|V`D;Kk zIdz6C#J7i!@~V2Tt}S%?)Hfh-QBXn~2L}ZUV*&*I6sE}+i%D7=2lUZK0=;h-4ICJf z{QE_irWE%vlAAbrTc2LI4i2s#vCd>@7D=Q00;u26ba zFlDC0BfB1P6|vRfNxS!huy#L-fs&a`EO$ndjg74aK(E-ixR8zEyoZ8@SI!fh>Ehn595bEjyZL`(0&5YOa5#Jf-__Wd4nPa5sS~i^{0{5>7UYS11I97T*_XIC5;Qd6%7W6s@$mf$z%DVQYMV zim5hnUNzgYtLP=fy|p79(29(|(G9luCA*o?)F~mA_;G@FAN*k9sF_n#jGEl{g`TKS zxOTXYsJfE`d$aerr5>VAAZBhKPB-h%n3XwkLpNI$%9_#Hx{4{bZPro*GMWk=O7VKJ?9O!)NPSATW&r`|kgl<(AjF1a%nIneZ zZcnpljTwhx(>^fo{~;Mh@VV{ltnR?%2psN2&`B56Sm6KTV&1N3x``|lLX_St%;b;8 zzNXP0_M3E4KQyy_2hW*dm3#q&Q6~Cgkw%3%ruol5Xq#Timke-Ho6?<=SmiMF{pK`7 zHbjrl$*8OJFV4nen?6KFM#jX}&iXIHrr_v^=HLMQM-zxwm7 z>HYF%+9a!|{Ebd*leyHzdNz3z8Tv{IF{%1kd$_)V)zx7S#Do@JY#EwN8>}sYQ9!?T zb+JaVck(r8yCT5jx|};E@^eIM%Anf-M{fTs&qF7ksqYwbJ62Pg%TCs9R`Xw`)tt3Y zKal+Ub4~x^OSy|%*w{xgPE6g>F)<{g9d93W1f_P9g# zrK{~jQhHMQ>EQBH4TIhAFf3)JQou+6#e2&59IjwKWs3Si-h_LWyEO}wS)i1$FfgBX z2sm#^7Uy6>--U>qg^Wyp_3$p~IS2`mUf)*?bcgxPuYBnF@!gAXGr8D3biDJW@xV1k zWf$+m6FQiL7$eB^v-SIb%>tC{hXhF@F9vy?*wD$I-I+5WDVh`;!uzOn3e%RsAp*Zz z+B2@m?24`KVb(-LQIVNSkPZ#yozQH-oLH(s|Ln@7U7 z3$GWbk_7dNFNjnbbI=n9Tw91tu$fmQ`h3*3!~oigZD*JBo|}Y`SUEEUcUStv@G)RgJRH6S zX;fT5mOqD9ts@WjWV-a3ibVwebTrnq`1U=Q(UA9XY;}Vx`x)BP?9%vuQ^HXk&w}7c z(ZfaGTYc!+2Ok)44AC#2S#a61w>a6o;I4F!3DQN1tv^Nbi!FO(&wg0%g@IH1Ro{Eb<_{YRu`u zy)n#(9N7(3OF|VDRp!d=B)=I%X$Ke5`0KDX{b7;r$!a2nWpB+GuP@pX+~QopX?rz@ zB@$|e4a$47eqUrV+}Yf|k#h24)6n*By1S`SRWWuwmn&Zrz2`sN6H8;{=}3~8*j&xg zx%uM7EUjn`ifw<7D)bCM==)bn@blS>y9xijk^(CCf)1jF?AFKS6uXhufyi znu>=&IVR)R&!?ZBv6sk>!wW+jzUh54w_VUF;(c{*;thwoe$Lboy@W~XNoie7{Ua*m zM^>9^LZ1#I9_G_W4$`hJRI|JjiW$_cKd}GIl~=Wd6l2W~Pi8569%%6OG=`$UvK;3F za>TEDlY}^~NSXp}FXyH_<|#2}Gt}&xFjH)Htl=RFMo^d?p-!bNTqGh9oz-*fbr|9x zv!GHXSD-VKOownG^g1wq2-DBDYI8Fe^UH22+y-_bYUrsqLr?u6e9%P6QnjBe24yBZ zhl{0aFekBUg3jz56;%DeWju;msx~(G@zGDI63p5E@Yeh`$NG}f0&l#H{SJP0r-$GW zFZPM=wN=;-J9p4~jt~76rEOZhCocwqwUx*n);SH`%ktF|^V{Z%@~-QUEidXAekUcSc`rPdIxiQEx>urPnPKxjzs ze%x+mO3Q??hU;ZzWuWinZ|jo(i^Tqvd;Ts4`ll9oUYvFEe>d<3$H&7)M)ZECh?48( zX@IX$qoWor@37B~hBXHV1~M>4aTxtbUcGwdb$vW`u$t@y4|&N~xBYn9eOP@R@c;7U zz6Ky_S81kQdw6SZF1ExQX5;Pwpz^%SYRowcnrO~|gw!F5d7CG{p^JtWkNuJdqe*!jMDOIyjlF9HG%RJ?Iwu?duvP$rSXshgJrzuAJ09DF;EhbPnZYwYD*kqoVbE z%(qg49FZQof+e`P!ERXfmjm~#_ODA5e!N@*Hyi2byt~|MViXk>O_1^zBO@mlbYIsp z2Kf|PG5SWUgHT*IJjHAC0m!+v? zYJhmu{i({{v95k?g#l(<-aQ>iRPG=f00IpT*zXmYE4ne+{g$!- z7^jS}vGh(cFq8#e#^K>1Sb(?X2iAd=$JX6K<0XH+uHHP@SwI1Dlq}K`ZptwrJJbd(_RwwP0<> zn~2{u;iaVl$v#gyB6g8Ua+6jM2W>tWbf)so`!9xb2)zAGg!w4&t9t(8zDU&8Fw#&K@frsM0^%n$gC*L!xXr~eA=eW(}Yfe7Ej zdnq50JBMa82V$`d!t+GacJZj)F%<7D?)9>o;(@z=D>B#E5+^h`K@Oc9JcAeR%o>6U zX$tkMa8x{>nFbnP8@-Sx=iOZ>rI?M==TPEk?)x%lfdxS5Bik!+xwj_bU+>|h5lO9`F}{?m*7xhEE?E}2fb|I7k?QUW6`t+qnw}^?9#Kh__$=)K)j&SXohr~juceijH z&`y*Y$V?itq8XtJzBlcuzHT3y9^>4#q@%@~?vJXl&HarQO_%yURnM@k+Gg7WE8N&Wj`;*$4NEJZ3PlHz z&)G{-2@?Lr?bXpPUYty7R?jlw@Yh$YuvLn2;qd*Ymc%)TJwXV#x3QZeK`*IPJYx(S zdF~lxWn{@5^LYozJ^2XN-2mkh-ftEUwqS9bRJFsw!pWqdQ(7`?xn=EEgf)tI~!htr3(* z*i&T;VSwyI9=3_(H2`~3%{fT0w2Dc+(<^_SvY?T_ z1+SAj;y=O#hJm#Hnqi_@r61svla1Wxs=4CZCF3R2U%;~fV=M%l6=W=Y zeDL)YMQFbM5DzOHcu%Ha&oU#83XLJcw4IC%Zl3zGA=*^57d7-@&TCN@C>(>OO8Sz_ z+)9O1G-2Q6?Ti4E#-@5u;GAB>CK5!k&))uPelvEWQ{AoczTtkF#h%a2_bXc1ZfUOj zy(G30I+>EMrriCWFUADX3v(7y$3{`IzGnU|@ux8|xFo<|Fr)KM5ekhnW_;=45p597 z4n{XX;+JB@IRzdH$5R@17@1BU>RjdDM!%E#DDAHuGz14d6%r~4GSGO|t@^1&o&5RX z`#Lu~7SS;vPun08iWJ(^QE^Eaw`Aelb@`#{KTzz#Fse>^LB0tnUz#&4XFy+%nB&yz z6wAVeJGrM4sAXI|4P+c`Y_+%urh2L#N<5+yA!^iyH4kN8pAI>FrOgA?|7i-8`gKi# zpRzbLK9oO1uYVuW91O>VCwJ~EM{Efb>Hhi7#39&GfM}@d@g9e~sSn1YyPJH|u5S_S z3m1Miow_Tuj#M=EB~(Vq!)!V5RYWuYvQ{#EWSuf+8y98PFMA$N)*z$4krKo;OJB+A;fx) zHG>&A*fgIOOMoGPYqtEgYuT~NN|aczx`xG;zu>^(b4gir&H_H|7DsOr(Jz)?Tu#^P zDh;p6ozwTYT9dA_fj7i!7nxMP&Y@nIVUqR#dB_Q=cp1lgQX|_v3G>wq>r2q-1;rntHvq$VC?m%7`X%`-a^W4PA;mE#V5V>sy-Tz? z!Kh-99lbx@<4R>`oiRa5i=LCT*c*)J;EqyNxlNhycF!|Bo-ss%!7DMsoc<*2YwG%W zwb)tOV=`KA8CmB=fmKW^ckP7%?eQVZAj&Qq;)Yqesvy%S#c*cA9xpu+Ea+gZ^cEC; zJSu2iP-tmmn}FqamlJZ>`^>e4>pS`T9c%EIs5PNiH4@6Vqus@Mi+=R+bEjP{75om~ zc(lY$dg!}C*N7Gh$&&pk#|#3STu26pem+1C+1euXQo90!4a^4j#PjIymojs3jbj?$ zRW#XQSM_C`zZ?3vIT-%qs8M0qy;!X~xq;lFpYI%#8Dnmqw+)Eyr^FOe7rJBpf1=k4 z|L8pl?bj1Oni;7WMyc=n?SnO@AZng57af0;h6W-sxo2AXxEV%JI4)lI@SZ6Hu2qP0 zh*c7~Luse@REY;tRkbyVGH6xNysZgs!#@SvIq7(4QU@HqpxjH!D$ilwmoK^wo94-o z8XrvbKu!vBQjW*C$}K%dem^f0}}zyBjY#i6StM_OrX|N zA(F5Cvr2Mqn1J19O#jxUX0;S4AjNIEa;A!uGqg|AB+@ZRFFjx_nNB4#_Hs2?w5mktCvsdpk`0pvL@N1LBho^~ao2tGRCrtI z+BidHna9??kziHN%c*UgshCkDh&D>`x`+7z&q+xTon7V-{ZAGFcn>FdCR4LJkyf~% zP;qgPf(#8f9i<&I!sc|(JY?v>lMgFHPx4++f1Dd!u(?I|DIys^OR98n_-W77erxI4 zv(su{JBU}d5|uV}rGwDejo`~ae}U0Bby?S`@>|g6Yo_>p11v?r-~wze z+lJs+=@Yf0pWV-Y&j~Ps3Bk(IY8^A#d-@dAFCd5k)YEn02&G2vv+ZW9CrFXK&iyE+T`+ zZ0G4s_P+)3U}S&yDHL|H2&W-5x3XtfJi1uUuY*MTf6R(7o{=m&C&bngK`>r{BrPH1hk80@Avu zT^(`u&SPk3B=E?M z>TPt&&LF~zA33n8Gqw(=5Ryz4)pJv(|L9tO^DHSTS%3+OW|3X|#oIHgURN`3g97jU zUvG_f-LVrW54NO$Zxu3sIJdH{d&-p(;Bxv|@JEL{SSz(R70Z}{O65+Myp6^HX&h6X zrF-5`7k<(&*y7)ViUthq;V{f?jovfKEi_`r8E{GrWt%T9Dv^Kw1|usgYowa|$;aQg z8I;?fu$1TB?^=crJ3xi-A;iUCF2XOZMpK4`!kYIDCS& zTgmpj-wG%CP5?K_-YEs4JME8)50x*+!1Uttxd|s!Ca6*bsn}3#fKZpmZv8UF;~;Z^ z2A21iU&Xycjm3MBlWNV-VC@CUX26Fxrg5Hq#8k9|$-+&`jT&qIPkJzQPty{D7zSI$ zBWp$nC5k>f9fe>$hut}`FzG2A8+FUbN$p3{WuIRfDjI|Mx*FsubUY6aia_07?J>rb zRFzeR9Hm_<_6>Yx0UD4$9SK-^iujzga zJs*pSj0f_S7)zr)8h^?!S#fl33I?r+5wJl5&AEb!5LpN!2CeXE!a0H0lfm7RY2pcr zrp7mtNruUKJ|TXo5?r2NCYk%c6Qeu1qdFjHb>A=)Ai>LY=D&Hng+y7-G{tY;ug1S6 z=%UIRqRMK`u5=aG>Z{QolRi&_qW}V(oKK0Z~!*39bSslCzGXID1vR`)wIW~PXQ7vEV@%r?h%kX@1h4Yh_8o-my z(cM&D*@WbyhJk$t3^jF%Vd_tSI)_s91>Zk32kzFE^RC95!=9d1uFk26sgp zaeD53lhYvmQj(TyJOZGv9m-P+>BM0(@wk~p$l@EKrP*NSW#qY9VAQ+r=kmXD%tt@m z_I*us`rurSXz_}l9^!yPKkFPl(fdon*w%3v7OW4%?=ry{a#mPZ^Cjl+Cdf%B7_68S zu0Ou1Mg-F=EF`sh0XfApLpQ8&ieCxI5_H~{))*W97%T5auWP^t#hG^Vz$mT^7E%Ty z?~cRIFU^n01H?Ft|8tXG%N{_CKc%Ehv$OTGo~cKw2*rGtl5>#0a262m?wH3uR4AZRA8+4e8au8d`oH3QH# ze81{=M+G&=JwPxDut45-AMSjc?3aPJFzPV5SGs-OA}VR+F*4OgNndxAgR3?K|r!@(0Q$*U*dWhCR7}oJfH2OH-eazh9b)8?}qVFyk#l#!S}Z zCEj?fZC=jg6c)eycc3?M(NA1c0cVbyuSp<<=HZL|r6^d`l&J~%)kshm10Fk@LX58$fhpt8! zsx9ol{h*&4{bvsMHdjd5M=nfw21=;?>WV=adQ8hq7!ywZKPWP= zDvDmpNth%#s{y3(EWaF~5h0>yKl*L^ImC_JqK=pl#_X2Dnjr-IJq&x6lHHL0LEYWy zDf1^?H=0_8MsN@t1wH#oLViHb5T?-@miusJ@6x{kxbsv0&lGJpJq1woB!W0-u~$F5 zjP!PKkoUVuP9fsE3(>k4QIXPc6tUj7*g-)VL;fx5Z|OA^LCBwdK-Yfsms>&RevJk@ z`q-PBjDqitDK%9|5&V28*f`~7JbUO0DEhYJpZHR4zqG|^5E$kG`e?T z&)$&Xf`CuklCkTY?s$gATfo;c^fUJizRW}MNM$R1mNCxvMSEvaEbhGp^%vH@67Iv$ zu12=ACdu2pKNU()>JN#n)+{WKeqQF)z2PYZ;zm6>fdwL90>hV=);{_TlZc3ltICqGim00f%QrJDx!#=z5S8Z06abY0WxzTqEr_6pOEeds)@c5Vn zZ(M)B%o}PS3$pwm)5mGvtaoc)yX|P=4y6`un1(rV3OAwa#iPAmXwE3q{x+m^El5R4 zIW{}CsWZP`-LiLZEd%N4n(MGmF}_ysEw}#>i-tG;9FP~hRL74O+ljTftScAxK}C;@ z3p~!%K8J_3cu%Y?l-2hD7tK*>_(lzbvIbnzr&r(Cer&DtLB~6LL!9(G)PMu|&v5|D zw`g56*TNeQFvZq4ALtuia6xlL8i+;=&|-fy??ZyW8u#DGI5?tppQaK7ZSiOl39?KY zUV}G?{fme3?$F)uZo=N${bF|ggm?h(MSs*gi;mJ8hO(b0ku(E7Z^E?OO;9r-ilc+Yd)EQR`1k^S5U(#XE?%XXHLFtUE8B;iFk(secu8)_X3~6v8z=11bcGg5XZNpoIx01VLo{aVfe}YZ zWIA?R9^~Zd_s~N< zU*4?Ujer!i{&)t5=+1Q2o2dla6iIG`6g|#6t?EuwLHjMXw(B>#Ub%DENTZ&ff#_3h zLZl^Df|uh0jE{bZ4P;$Rm0XmOmxJedXlLk%yWcf44xH2)CjIa2S2&#QtoDr7-}fS+Y|j^H#7#srU!`QZ)X z)i7ORz_xhKzkjcg*_a%e%+aA*)(YP*bv72CH#`oNmq{)%R7vFM0)L&X*U26mc))2w z_=_^(H7Xu6`36H-h_oP8{7Om2uHWs1{;5f>yrI!%>!6=EoS*;jUjtLSYEBtexcE1^U107}a}m^!{Ft((z|?3t=< zDXBDwOl(dh;{&3Mv|_Qj8hxKZ1}>8_$?A~Ld(6xwE+8>+?;l>MZnKDpk!a?^g7t$- zL{X7EaQ|dz_;^k}` z5TZrgf^@}_pOzEeT-lHG#{dy=UF*#(R{=LLKfTN3ZkUmk{fMz7_lfnA5_jk<{SmsZB7%T zqoa%QMvg7O?MnfN$qMqe!otG4D_`boeW{ysNa)r_>oji=kb(CVEaqA9au`Z?EcfgX z@)DjcHdEd?MWJwFF7oif@S6M%g_@j8Hsx@YgJ1RoyDmBg69hHptr*8R&pnR9T82#=D}Ga3{7U28(D9DeS|)QI1{6s1(=fD$$a@ttH8^HmVY3I`GxHfPYzD;;hkNhier5Zxc}?y&cl<1{V0zObq0*6kt-O`7YR z7oPjw-Q_v>MR|97wbpHoMMQ)WxXY6e4`(QGkl#44gXUQ2+p*{@N~C90(#5E2H~h93u0d?oX^~jQiU2P8VVjypU0nG5P7+ePPC@?k1+r4}Kg(SSlD<`6PFjhj^?2%U zft#P;ix3Tm(*^EF69IPv&D{BF5uaOD=%DPFfgXAbKOfF(Ks5#8a_S&;MbuB)82xZvu>GF`i+F? z3|IH-csvSwfX(E-YV+cEV%WBqzXxB>A!qNL(*st`Q)2PkAb@g$yB1e(L@ZzW#Bd9G zU7S=refV~`5Vue|s=fa6V9JxxVe!hQK0cF5#{71ok6=dGqrN7Vq934Q?E@RCBg~3! zb|LAcAIPew53jtKS-9~@P1B+UxPU2^pFan`Ow;pnH4LOo-YY(!4j>sh)t`A#cf^+a z&6>cgpLD;wtcR})ftqe2A~rnbPfW#vziZJPYsxslOg z;;#9tB!Fk53-!-!EUA#<@&}CK&fl?cb_apvoSDv08+-ere#ft)FpHay=bXj73VwPL z0m6T**J0aV)|Or}{}jG{RoqyaCUP%KmgLu4|uP-Dp@m0#(U<%Q~e7u?DZWSG01 zP-J*9@mz6i()GQ{W=PQu!#C92YTWWNeee2b{jN)JVbj}fu8sUJap}?boQTnY4VQ{l zpSQN(4XT>Ey*V{p-4AHYBY^ygu2Qp2`0Hw>&|!&v-fVQzcbXz@!`y(V(O0#QjUO8c z3F4%b&?el2*aHoyzee3nfC93_g7Ja9afZhC9zXz?WbZwHYrJ+WAM$v-9Lk*>u3Ghy z)>`rACC`{2wB5|af9KmNI_E6z_C@FimSPTpZNsU?FT^S)R@uqRH}QeAXlmT@Mse67 zBlS)qO~=@a$NUFfo^5yH0A2aod*VAHu6G8&&OI*D^EZ6V>7Oije4mdu?CwSyf>W~{gvK^;NFIt*0BRR@mN3>3z zR~^oMCsf}(sJUduW1=O@{mbR^q8WgOnXS6lOfZ^2_vb35wqJp^Z@XQawgk)vPVIXM z^AM$~si`loXPi(^93ejn^7I1MZQpdsjy2ML6zdD3Y~~L0)xSS z-TMY^=_y_}0>jaE9}I2^AxrWQAW1JIvQTLuD-y5Jz>XXOl53p~%IHp-*MXt*FOR@u z(fCq7>+0sf8zkAth@Za~o>U?f2f2RBO$E{Kz3L$B`O(V9AG=(LNOfJrrYDa%H30Ym zkC&}K2WBm~eV&4erW`kgK+g0lBeX}!SW|{x@1c6-_5nTHILZRQ6)}VXt+B;C0NnL2 zh#Pg6a?Wz#OtF!?j|%O9-_3`F-pw1GRhx~e_yp;B)CjCSZ&SQTGn+~jpWXZq^>H*J zr7Kw6-z5hY+tS>cbMot*IB{S%@7z!gb^t;D%b*J3&MIO7hsP4 zlIZE?B%Yq=>Pvc`AC&o=7>zSu3wJ-MiN89R#dtmVDH7|76(IbYL1MFg;iKN*oT4#c z#_LC!GaEj4QBo?9C_j_F^fONK<$Aod(;s=PIQDTBB+<0ty)B09fwu{qy#-s3C9sRP zHeH2O4%+-CV5+Tygsw&+5cD}jQEI%exWx6%Ui*tPB@fdTZM-mPATiN_b>FdUS1ebf zI5L+nX2q)MU7IDQD>4mKZ2W|efx{#FpgDY3bWlw8cw2boY-(qHs@+NMv@4b^IKdg$V&1;zWGE?Yo(U_DS}@_~q7fb~P#Zwny?!4fDT66MI9 zrLzF@9wU9C8VUY~C7u$_S%?4$^)ie=i{n!nU%9dgbq#;DINdoP3|U0lQU|9uBnUuo zLVcOlHBKl)4)dnGqvuu&Z)|o#_1lD*mTp9kK`cOH8Oo_ zpN@NzCx+o$RBiTZmeIRc9C4{k{AR%j|E@`@;lt+{r@vZAXZ!zGdtV+7_51y8j4VY& zc0#ryk?c}q2`RF~WE)e~>=6dpBC?f4%9edMBWsozlr{T|5hZ(!r9qPYxu@m(`Fx-2 zdY*ru=Z~Mi?rW|w@B4k<=e+K7&VA17AXEnMeP`8neyERyqy^Q;UCaGW5pKmE*WgBU z9I__d@R9xoALO%Xbu@n_Z6CkJ4=LN_Ff8g9@sRQ40t-ZakvY{NRNT?XiKS6&|1h{? zfEn06^$iFG_Q8?Cu4Z(i*}ePCSYV6@^Z8P5nsgxDvzsQ)gRU z#}!-Y?T{nmbRJ9ul&m-Nh??=hO_vQpsddT`RUS1fl46 z)>8OQ;Lc)=KrSxa_Gs(U(~4u9Yzv721jKF+^8{9jUy+8xGkP(+c@q!moFRoS2SIh3 zs$(|mI=1PM*=R_xL<-ZGA>ZRUXgSxX;h9I)ic49gYwDGO?eA%Vnd%iD{z}!Q0jbH6 zW*~3RhTCG^_;zV3^C*%RNL>-N7rK6r0WbJ0jna@qHEAnrhG8{$4F zc-rUt>`7P`55pVg4$fs8x9YLE9mn&AbbdL^p(Rv0fnPIqbkQ?B|5u7yiq{ zo++5G#zoAWs2;Xh`5?QGU<6pY(cf$$32N-3hD@x1NqW=Moxy~yRO4>qhL?rfJ<79P zwc4fIdGAsKXx0|$q9m@|8bMTBmhya2_^FvFcPw7lV@IgtX>!BY9_p;m&$_^xw24+% z^Nf6FhZUhZDx`L`hT&tX4merb{_D$zVk!yC)j(J_;QLapPHWCN-FMp!w=yxsbJ6@p zUS6Q^%2L9rz1K6#E21FYQykA*o#Xn2fj~CddmarsMY}|t0#_f8`MvR<5BCF+Aww4KMcH+UY17=*R zE79>8+WW;*hUoTa;KO%m5{%4m6Q0#J8BXc{peo}8h70CyJY}lGXJN-Xx`jGEKO25} z;sR1aMN)c2HeQ6iwroEseZkP!@>H>ydhxE6jW}X_O1KIbDK8{Y1ojtxMnka{kKybwaREC(MiB{ZxCY#3#L+@jql z5`r(G!2$#agJ&=6whNsyT$3)vcO7Ze4H$uTK!o$FbC!%uf@;f{PMN08M?f$vH$t7C zvADz?D$CsI(UL1pZY1All|5&ANpZ!NmXQ%I>&=%;ip$pG9FvNfcyaSU%V}L_1(os5 zwt2NV8nM;r2U+}n)hB@VxLf(YtQ%#&eK}v-(%Zzk(eCre;hBz~gKj0yz5u}x3#^n? z%#?g9DfN>WT-*A1Nf2QE#pk0p0qsHT@jzumL(2z;QrI#|^5_BkN*BUTEw6~gFIw>0 z3zm`hp=gxfW$8MP%EjTM`}eH^GEQ;FRX1865*nlqWbZh!5GwH1H51+YnxVk9YQY7Q zcp#o~a@IUMa+4Z&$lHz)pu`V%T-RYyv(g#T*vm_<2ZA%lYXoJrf=x2a4dsP2g>SdT zaR!db4PzUPv5kGf@6KZ1ymC~z)`&rL32r5oE0%84@{77K)DIWc6q#lNc97c<%JD1w zeU{ICWO2*$$#T17cBY{4QB#oen>4rf{xtEsV2@fQIxA1fS}_pN8C`Aj5E$y-C*8O`&XE7H`Sqs zMfL!7@#u+51=P=<#fOfce%r&H^I1^Igx2|f&-P&ta6>Edapi>b>-J5&ddyl?dCs{? z8HmRtY{z01`jw8Q3NA6}DJ&RVP%tpf!@OG~<>D%#1a_~Dkd0rlT$q^~a^^{Z)Wog? z4i9a6&mCfUPf~B1cX`YB!e`NL#;0Kml-N zNO(a`kMMX%2r}5jKz@`^+%^hXx4Uz9w}d|G+v4fL6XF!r4TogpDOWj7gsDMpcZ;T_ zw{Iw8d%8O8rV*cCbNfZ>mad-uwI89 z=N;r}n=gbsB_~g__p0Kr{75qVP8p0;8x6uXUU;LUewV?*>E5SS7)UM z-uyguSh&hJmkSnWrrn6Cn#JZdQ5dSI69vKDdycEBA@-%Kx*^v@=pF3%DVa_%_nW|3?hDIoXW6M42)dgh*2TVg)QRGvfYAYg<;tO;!#BKi$-Y?C)-m3^6Y~C?%_EWKJ(>mHUsTn)* z4SH)5lE?dI1$b^)0o&_B6J(~hz{#O?JHXUJoWj6yc&i8YwLyEu!)=8prh@~Gc!)Dg zcCiFwB5~zcsN_Na;>pE!l3fEje6fABRluFACIyz>T!YhgDG3u%%Z?oUv{A)Gjw9{}~)%rf`BVDgI>fH@@ z>vUqLOTSTgU?WoPM8)e3Rw1_vjYQiPidZbR)hI85o6$w6%x5={Az!c7xTR$oFg=I2 zRK7%^BL^+?#TIbCt-}+dV+X5Hf@O<*Y*8N|*f^&WBb-_pXRZ@1v0WhL_lhRh+_uTk zY^0Cm%V^YJUrYa;3Q}q;SG=e%$$$!~JyzEh-t_ru2z2obh3^;0_O-PW;i^6ynZ>Gp z?x)BO4A%Y^H(&Bkd<|Nfca(Yw)u*&(0>p~}epHUogz<2XNHr5Jq`!3wi!BazZRHi3 zJ353W;CWvx2-Bg+Wr^(tpF9Ob%{CooWAkx%ZnHo{{emRS_hOt%%i#$gXY{bKT<#U& zaH1egBOwu!GCHJcwsLOS(eiWX%k=o*cJj`~bg8g|3Wp~q<0u`=x0$nFt2S7%$q$Fy z#L3hSh=^n-uhO$sV&}BC&M6kUzI#$y?&=&B?BtajD15$W~S) zn=0S^x(ANFZ}o(g!hD=kC`~}1TTGcPjR_Sivoera=lJ0nsCRAxkKewk-@2nX;CL#E z;j96io8Qk`P40GS09{<-hjey|x1xTYHSL5tJwm($7R^}lY+-PR0;p~7`LZg(2_?%y zJ~|FPunZxTp5*FGG>KXZ*TszdeB0^#@K4jnJn0ifn_l1AVUu5f-exq*U z0#)M%QOq)c`{cKi5I9JfS`@3j;Q5@T)IRl-H>(#z{B1+mVuw<%M=dYRoqbwZ>$={vd#efE~N{1vPofXC&W!bl0d%4FJG72G(y zUB-Cw*nSXpG7UBibIN|Mt7~p)dl~-VzUgFaU)irclzT$!P+Y7vzJ8Kf>Ip!nbs5_| zt>4HX)6vo;aKvM&EMbakRkAvu6qAbvK&eg}%z(i(}OZ$r6*F_+3slyc=T45}I4b!=o>jMo$Qc&X{PPl%uU zNh)y2FJ`o!kPt#L0|62#+*-c*n+p=Pam)+g8a4XFbV<2Ipq$G%ys)@_Jz%2W#7QGS z1mgQ;)$?Aaf4y8?IGTz_k*<=wd{~+XF|~&Q zJ6sd8M#+DMhr=T_vG~K>d5FZljgS80+LUu-8nE=d4;wQ2Mx4x-V6x2KzwWYNY9dmu zc~(|iG?wtwHPs7V8F}=NqM+ec_IMt<^=Mc>C@%9KzxMb4>0{_|2@s53R+lQh)gC0S z+%hn5gAa!-AWD^ONd`N2L|?5CJNGWnfqE=$g~}N2b26JCG(lFL$5RZC?*a;&$(4H; z9R5xVVico5QF0*2bmEo^GFo?IFxbnt|DMEW2JJ#w#hZe;5mn{foYO-bsG*ls5Awv{ z15WID@)-l=qbHwGTpzF<*HVAPI1(aYt0Mfej2vFs*X2Uj8oLZSJ#5M=&xV59_ZknM zkIsTj$1Ppf)5Eo~7l0J?49TFxHL1&SceHmY@K<8p0`3&mIIe@T)lf8b9mD!)XE0HW zd%1CoR^iRSS_R?qFk{Lx6(zY|Xo#Qg#cwiQ+XnlCpEZlj)xFq>!_nD8TXvG zkLaf}-s5)V#Z6p3@(5jiT377c4ZhcuYm=8K4L#nH;*PAiY3r9Ew3jHc|25@m60^ zm*~Ry$zT`-0#YyoP7+bF$eGrhY^W9aeuV~{UG1=MAOC}5ER)hSH}ms-Il}yB6eN{I zA?^2seE=o;D`04E(4~yagUShWKC8Fc=*IXX69K;p0f#%CrtN(blqMdbxA>rkUYSGt zb{=~vP@BW(6tbLPTG(D~EIAB(j2Ck+FYY1mjJ6-syDww z6c1KH;HKOQBb0hr6HDldin0)7ot%br-3YvbW7j1c3!x%s3-k4?sw*W2v#`zAU+;}q zV+#t8j_gwF-(`CAsC>=K$iI#@)f7FNS8RGyfs2`z9O6+`|7Ot+IM=MXuXW>OUwnVN zWbXJ`hjcn5A2%1Pr%((rzgH?jlOtpH`5eCp9mlFQzrfJdeBatMD(3;bUW7w5)qfGo zCQxri*6kexwhHj!6ZD|q>0+{*VC>t)Jb*@U@YenvIf z&q#z|b~F-OPc&N4cC5Cxq7O;$_$W{}jS~dxOHw}%v+=)Wl=@CHTqJGYPxmv|ssC+0 zR0w$jyujR@E*``mJLzG|iBA;w_nuZwDSKTDjN9r+Jfx)HsnG7b`lHD%=_ zXn&RzvyBhY{Rd5vHUKP8mS>3D@X}bDa$jA!*J;BIM`Lo(E4`Z`>gEzx_wp!I+G;uC zUZVX=i(hgmh%xtv104oTe|?~A8;}5YGJD~#Fi74nR6KmYszZt5Hkky@b=mcY4*bfK zksn9(5fousl&ygdxzc5Kw6NlqRqN?Tp9cE^HA>Du4M#EH62u$Mvdads>XTj?E8hC_ z!Ml*=1dXOwdkC=544rPKSyyU_E4jxqY06(5Yq0)EhZiQA|7oBU$0j13FU$3PtPXhAf z9)8$gN%Jo_T1*01K$E!m9p6Kcdwd}z+KC2Jm~#==jr z*~X|D8!2fJO0@xXh>7~ytiexgbGRKd|Ja&JRTme=sCmugh6&|){uOU3&iaqflk(~p zdWNc9Yo7tmDL{PcAB#6ajEDaY`#I2N`497+`nWMwY00zX4V_`+BYb^y2*{1^OBf{9j4>K zrRd6$T?WHe#Y28Dun+GIyep|_8;*u-ezbp;_&%Ebs*_Xk8M8`l^xGe1fL`mIzoFDX zvhlMbsfN53d(W>D2LjgW2Pwr8^7$VIM9+OfR+tnOMNtr(jNdrWht#ovcjh0j^D4d% zn3o3ecws0MTJF1UR|`lYZ9kUV)~e9Z&@7&fgu*B&?=ru*@BXSXaL3No-TL5OWXyiL z=(%@yk`3N#WYpIy=S|w!ouL{RGsc+L=na>c3UbRiOVxLEcc~>BLhn2VB4lhjH}Flq zOQZeFowS`k&dx(LS^9r3Fu5=etP`)IHJOyiKWz3w(xMP zlh}PbpSI$^<0X2?H}aBedrghjxV^gP?X5#AUwnQ*Nt>aJ4gpP=)0al@@*f(Z!a#Jg zX9~b01aaMDEjiXKj+ho=`daV?G5x)(phXxq!9cM5*!9vuFhOS`)<_Jo$|V7zriPe< zpRc};Dh$Ui9?GuOBRT3tCOse#|BUGl*``v2$hWWrN6M)cmQb~wX2qalpHo0XgC!`b z5at&fZm3UeJQ%PLMakyByQ+%%I}Upwtrv{to}|N)XJbM+oe%|Y(7YNPqKajMP+}zb z5Jdtczh(zCOGC2JR4#wR-?5r1rzmV9mPCcjiq#hr54o%JUL27vjK%UJqbWiN&lx50 zOsKa=83G-m=k%%}6XNeUGb08_lqQN&YC!W;A^?fpDOSt?GZM*Eb~za9)E$xJNZWvr zN?xSG%q5^s(_;RP_4V7x>vjS10<2A!4l>nnHHzRsCnZHYauPOCHQRbsmI@)s(c|?1 z?y1voJPiJKeCpIRSiEED6xS+`>DUTO=@p{hJ5^F~a8NlP<0p--P3Ce@V%THml$whb zg%O8aivNy}C)@>+M0=e=WRy0yoS7s4M#Ge>86`bEWi`M#mo1VZLkQuBrf1p&K9yPW z&?Nrv_;8m30J1Pq3Ko|zLsPn#|JycRAwD?D#|8?f1*OjkNPF6)RMzCL#2<}en(&i7 z`9PZA{ny4Ch5X)FO+haQZ5t$PrayBxPE%7;!$!@_e9YtE%YeY(kl-MmwI}YpBGcUD zHAnmYaIjnq-Gx;$BBN%VAIBLn)q6t5@)y$?>-;y`@8en5D#2JG-zhd}DtpCow;1Uyk}eKIle~ zUh4|UeSs~J60>qHpsvIB>qF%qB__Qa$;Rt3V`4zIjL)AY-p#eP<(Vt`iyZ3>t3Y2c^ImBsJokL~}bzCTM=lY!M`vUg9 zbPe`t!h;0D1$v%ZCCU%2_|+^~Dk8vcbGH`V`B#zEbp71!+6}|U)?Qn0>6?p1&z9Xo(KV!{ZHda2JyZ=|l%>CZ<0UUyye!X?6@hj9yWx?{YMcaZcJ{)KYTixq4rS)7SkAwGx^IenS|qFrPZ`JvT|@@frJ!BdKm8cp}-KGs=P z&iOw^v)0XMtKK)8G%4iO@KG^4igWTly|Wg3rsUM;jT4CUR`Z>zp%|6ydPEB3skItb z5Ny8I3zNFrSyi`N+(0x-VpCg^p^kHeI!EjK_1Yd`(vR7$x;YxzxcE5tR0@u|eomStM5dlij_#gkPE{lo)4 z#;Pl9-ygcv`$W?p1^Q?)vAojTWa`qDkdwaU6HWCZLc%OF?+h6%9bQGsITv!p1nwHx z90b-NW&FJ_{#2sLRx6M|l;WERUq|e`Qe+Ch>$tFH4j)R}=g$s2QX|DqgRnfcvb?by z8gunl<*H7w?})gbQYg$3NF9!L*$b=T)EDV=IazT7{w24u&v*g;g)9C${EH|XcU89G zla0ZqZodMF5*!<5SnM6OTWf3Gl=4Xy78Yr1&6N4Y(2rC6*b6)nTAIfg!TVo0dp#yB zQqH1qta{J;i2CZO_{L8%0mNI1nAvYz8CMH>z69zM)6Fb9Wh?K#<%*wteZ0Xh>460)j8PWomswM<4l$s!=;+Re!n%9s)v#>tge@&*c`v(4baZ z6Ln{zv}`&4;#NfJtnEXeyrm@H^7<=GT|6dkmrk)dY_NsVhnZ1gN{3z_!voEGAl)7u ztPGSwxfp3CJuqyL7|6LaBOjH<TdTAFyi4Dzjd&+7q%NE^Bof|~n!pNhm|BW-tcMG@0J^@4`Msw1;Ff>KL)m>^6&A!5` zIVm~ZPg`Ih-NpJ!|84P{mmP2{D6KRsJ~)9jNy?XTaF?huELMd>B~$1qU)CXXncS`& zL%IAH07koJNra6~AVKscr9GUluQF6unzb!dc8nOHGv7@7cC~oXT52=%7cDga-MVzDk>+pBP4hz zRS}rU2^X_mlna;NQTmYhU)BVNov4|b%T|SM6aDZ%nB$yu9Hp$JG1vEm6!!SOwjGAh zQA_Ygp-Sa%ZAZYoz<=UbrN8*0ZrZrJYlw)5*vx&9Q=ErxKB~=?{puQj5cMB1LnDxZe4+Nqo%V!y&N8;2iE}5$M4*Zd~kRslC?fRM_sf@WoaI z$7bRf4R?~AmqRIUnIv+aAI+(#Xg=irrtTO&2cE&jNV(0)Xw}o5HdPb6-~1IohT~Qv zb(~RPwOdz0%XS~f`xjYw9A%e?I_S(;MtybZ3(p_GpGYxyJ%p=EpE^WYLJIdiI=@%9 zo&V|iag&UWU7}8f)!G?u3tmv-&W*OmX2F%(5Z_Ls= z3)7JxTwfzY;0R0@NM0j#<4ldH+I_1h#s8`s?DJ|E@2Q%yiCGv9D~OEt*Rd#N*q5yGeDkWAVD zoOLy9id8Ms2Q!+ha5|;bmJAKfBD5rkqgv?ZNz(t0LP*h2!dKG%WJaYov!lA=p`R2C z?{9c{O;sr&GArAhPURJx7YE7+N&c~xS9Jok0HL6HH5tD}5o>f2cwC|o%|m}YE(N&J zLbubEC&G*KOfN5e+0vJhQ42=eXoP*0CJ{Lcp01D_HK=T16J&!wBfn*`_2gJhbWCj8 z#bBKfxLfz~H}-)|<+6vDgBknF?$??`!{LUvs#=|HHWtVQf&~cXeexuF-iVu;<~pxN zEq3g)#2`VmrIocMp_j(t#dv&Cn-g=U)B!K%mR~jzMK({}m1TsoHaAWvCGcIl3owD> zd*Ej@nwC0cQUL5?{(a!0=uyTT{^O!(TlSF3&_5q{yn7e+;l+SRT3TU!4pfke7Pt-A89WnyO0SKtq*>=kXn4Lt*z2ZN@C~jKDd9% z_U)l<=&`oy6P&I}zn4OgN2Ihjrfao1sVof2QxyTvP#5V9Ncv67Z6fa<>}}RQ_niCi z^y{l2MDN9&CE^`<2S$-m>JV4bEz&4*fnP?*(wmzG#H zA;@t8US3`g(2+pL%?(p>^L5Ko=X(t&6WyW~Qu5(fAJbMH|BU&R)v@3pb)y^vDdNUx zkabA@4jAXFmBE#BHu6%XHQ=@$pIzY-c$9OWA&p)h>S&0aGUhZQ$YEfM1uN zVBx8Yo&~KQQg#l!7Kb}Q9O-2}hA`Tu;ned8T-*~Qva1jz$G_wEh&;VC$Jb{Zb@*Wu zr4-tH%Yb_k-Z_|i)w8&vZW$2Etu*eq`huoNIr;fo==v2UJ!x7HHem{;ppsUDxp~?g z0UAWO^>DM9hVQuLf<#q|lYyf65w_AC3>{{H(`*TX`8ytjQr!n35t05gjS=vma`P%j z)=kp(g(C~ykraFxdM_S0g%Kta6j@{7qSs^|z*+$mu;{Y`^*L~HbNhOlfk9bTQU4s| zB5eACC@G4dKqn>k%aPV4tuA7kxjy z{ipGN$3Mb@-M|5%m8Gt(UMnhnGjLMl)~Rcij}4(t;RI}$;`3;@mVStAs}r*2l)N9= zz+feNJjNQop88Hv3{6mYD_!IcguB%XUhP44wwY1??*kDu_&+5_r-OG;fmZc@2HF<$b@L~;R@?bz_jkS}rMNYkuDqE(x?W|3^rhSmE!^+- z0L3!V=!r|9J6P?P5A%%0&XX+U9#gZqNr*0nFf4atpKgj=Ku4;}>dfGST2TgafN^c;`lVwrCx>#HYq7!gpZFju^t(Xd6~L0fZ!iUT#;;Htl*0 z1ixdu7}B&mE4c=GA9SUw_|JgOT?t@2F72w?qNwVn9uzcV zYKV6J@C0b#BLQ?|4FsgSgp({Nb76D^tz&LYmaet4Y_-ePV>a!Nd8x)>mTXCH0oZ)n z8}Z{F#A1U%-n?QBDWmDG^^s6uO3`08Hoh`ZdKfMhYXx{yKe01!z{iiUpQX=pJ?tf@ z|Fe|7N!n}L_H|<25FZkdc6NPYk*B7inJvaN#&@5sKjZUd?-08M^0l*LQ3w{)X4wE% z*m6K>ZsO}SL5Q&gxSoOdYR!yVq<_==c~P(!)ORRuf$}JiTkuzdST??pSZ?{b3yWfd z38#&wxRKPFm4J2flh3KcDS`P@6@3?<0r)_;zL}&}1XSSXVFKLhySwXswxESqtvQXa zjygv#UBfMuN0DI@Da*=Y2hgp~0J6bjd(X?z(xl0Sus&zOy~LN}Z5f+X>7KocqD0)+ z>a`Nvic#hxL##WXe};@tADUY5ZJO#ghMCh0k@FAkU?muu3v|wW2PBGd`}_N!9<+j% z7dD`rt9C1CQTABcOKK{_d9guu!xt9USFX5~1Z^+m5BcaZ>I%9~$AX}cEWBZ1-S=xx z3Zb%2!Ci?G)FUKHHtnuyh^nZll;3`Tcwu>2mvGKMMF2J-n|;ztV0(;xPvY9Gp+htQ zu5tt?$4Z99VC15mdGY5zdI8J;_D#QMnC~X`N{Qp_3|5>cxqVzwK^-Defzg5GZK^G Date: Tue, 24 Sep 2024 22:06:48 +0200 Subject: [PATCH 54/68] Remove changes in existing mock --- test/image/mocks/line_scatter.json | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/test/image/mocks/line_scatter.json b/test/image/mocks/line_scatter.json index 0c588fe11a1..5a38f3ab590 100644 --- a/test/image/mocks/line_scatter.json +++ b/test/image/mocks/line_scatter.json @@ -33,14 +33,5 @@ "line": { "width": 10, "color": "red" }, "marker": { "size": 20, "color": "blue" } } - ], - "config": { - "sonification": { - "enabled": true, - "closedCaptions": { - "generate": false, - "elId": "c2m-plotly-cc" - } - } - } + ] } From 0113d904e820d0fe2f443e7a7b842016a189546a Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 24 Sep 2024 22:16:49 +0200 Subject: [PATCH 55/68] Update baseline --- test/image/baselines/zz-chart2music.png | Bin 32693 -> 29125 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/image/baselines/zz-chart2music.png b/test/image/baselines/zz-chart2music.png index b91ffc35c956cd93d44cee811fb47331d83fd124..95b08021ecf77e9de66b27c0957089cb07c9e251 100644 GIT binary patch literal 29125 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
rI=V@P-b*RB=vMazf*Vt9jFDYhA5^zh4;zU8=5 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 literal 32693 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 Date: Tue, 24 Sep 2024 22:21:18 +0200 Subject: [PATCH 56/68] Remove function that was introduced through incorrect merge --- src/plot_api/plot_api.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index c33d333a2de..3ad45057585 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -385,13 +385,6 @@ function _doPlot(gd, data, layout, config) { Plots.doAutoMargin ); - function saveRangeInitialForInsideTickLabels(gd) { - if(gd._fullLayout._insideTickLabelsAutorange) { - if(graphWasEmpty) Axes.saveRangeInitial(gd, true); - } - } - seq.push(saveRangeInitialForInsideTickLabels); - if(gd._context.sonification.enabled) seq.push(sonification.enable_sonification); seq.push(Plots.previousPromises); From bfbbbf10dfc9a74c04975322b6ab13bf6460942b Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 25 Sep 2024 16:56:22 +0200 Subject: [PATCH 57/68] Undo changes to jasmine tests --- test/jasmine/assets/create_graph_div.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/jasmine/assets/create_graph_div.js b/test/jasmine/assets/create_graph_div.js index ae6fcd39b52..9791d46018c 100644 --- a/test/jasmine/assets/create_graph_div.js +++ b/test/jasmine/assets/create_graph_div.js @@ -5,11 +5,6 @@ module.exports = function createGraphDiv() { gd.id = 'graph'; document.body.appendChild(gd); - var closedCaptions = document.createElement('div'); - closedCaptions.id = 'c2m-plotly-cc'; - closedCaptions.className = 'c2m-plotly-closed_captions'; - document.body.appendChild(closedCaptions); // this does get generated - // force the graph to be at position 0,0 no matter what gd.style.position = 'fixed'; gd.style.left = 0; From cadfd7f18f30818bef72b0e50724ac23cdd74bb0 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 25 Sep 2024 20:55:55 +0200 Subject: [PATCH 58/68] Revert "Code readability improvements" This reverts commit 129eb3b57779d2a01aa3976a92e3884356242a12. --- src/accessibility/sonification/index.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/accessibility/sonification/index.js b/src/accessibility/sonification/index.js index e5d3d80b78f..77fcbe4e624 100644 --- a/src/accessibility/sonification/index.js +++ b/src/accessibility/sonification/index.js @@ -31,7 +31,11 @@ function initC2M(gd, defaultConfig) { // I'd rather the callback be given its c2m handler which // could store extra data. Or bind c2mContext as `this`. - var titleText = gd._fullLayout.title?.text ?? 'Chart'; + var titleText = 'Chart'; + if((gd._fullLayout.title !== undefined) && (gd._fullLayout.title.text !== undefined)) { + titleText = gd._fullLayout.title.text; + } + var ccElement = initClosedCaptionDiv(gd, c2mContext.ccOptions); // TODO: @@ -40,8 +44,19 @@ function initC2M(gd, defaultConfig) { // Furthermore, I think that the traces would have to point to their axis, // since it might not be x1, could be x2, etc // So this really needs to be part of process() - var xAxisText = gd._fullLayout.xaxis?.title?.text ?? 'X Axis'; - var yAxisText = gd._fullLayout.yaxis?.title?.text ?? 'Y Axis'; + var xAxisText = 'X Axis'; + if((gd._fullLayout.xaxis !== undefined) && + (gd._fullLayout.xaxis.title !== undefined) && + (gd._fullLayout.xaxis.title.text !== undefined)) { + xAxisText = gd._fullLayout.xaxis.title.text; + } + var yAxisText = 'Y Axis'; + if((gd._fullLayout.yaxis !== undefined) && + (gd._fullLayout.yaxis.title !== undefined) && + (gd._fullLayout.yaxis.title.text !== undefined)) { + yAxisText = gd._fullLayout.yaxis.title.text; + } + var c2mData = {}; var types = []; @@ -52,12 +67,12 @@ function initC2M(gd, defaultConfig) { // TODO: what happens if this doesn't run, weird c2m errors for(var codecI = 0; codecI < codecs.length; codecI++) { var test = codecs[codecI].test(trace); - if (!test) continue; + if(!test) continue; var label = test.name ? test.name : i.toString() + ' '; var labelCount = 0; var originalLabel = label; - while (label in c2mData) { + while(label in c2mData) { labelCount++; label = originalLabel + labelCount.toString(); } From 56e582d73123cdbfa25566fc2b10b59346077120 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 25 Sep 2024 21:00:10 +0200 Subject: [PATCH 59/68] Fix syntax --- test/image/mocks/zz-chart2music.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/image/mocks/zz-chart2music.json b/test/image/mocks/zz-chart2music.json index 69f941dc1ed..ab9a3fa71d1 100644 --- a/test/image/mocks/zz-chart2music.json +++ b/test/image/mocks/zz-chart2music.json @@ -54,5 +54,4 @@ } } } - } - \ No newline at end of file +} From 8e55f98c21ed6bf1ae60b52d754447d1a84d5287 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 25 Sep 2024 21:01:55 +0200 Subject: [PATCH 60/68] remove comment --- src/accessibility/sonification/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/accessibility/sonification/index.js b/src/accessibility/sonification/index.js index 77fcbe4e624..52ca5d32fe9 100644 --- a/src/accessibility/sonification/index.js +++ b/src/accessibility/sonification/index.js @@ -7,7 +7,6 @@ var Lib = require('../../lib'); var codecs = require('./all_codecs').codecs; function initC2M(gd, defaultConfig) { - // TODO: should this be Lib.getGraphDiv()? // TODO what is there besides a fullReset? // TODO Do we need the capacity to add data (live listen?) // TODO Do we need the capacity to reset all data From 86d75d86d777636625bd96a5eac1e377b9ed0f8e Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 25 Sep 2024 21:12:04 +0200 Subject: [PATCH 61/68] Remove todo --- src/accessibility/sonification/index.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/accessibility/sonification/index.js b/src/accessibility/sonification/index.js index 52ca5d32fe9..c6a2cd02165 100644 --- a/src/accessibility/sonification/index.js +++ b/src/accessibility/sonification/index.js @@ -37,12 +37,6 @@ function initC2M(gd, defaultConfig) { var ccElement = initClosedCaptionDiv(gd, c2mContext.ccOptions); - // TODO: - // I believe that title OR title.text could contain the information needed - // So we should stop if title is a type string - // Furthermore, I think that the traces would have to point to their axis, - // since it might not be x1, could be x2, etc - // So this really needs to be part of process() var xAxisText = 'X Axis'; if((gd._fullLayout.xaxis !== undefined) && (gd._fullLayout.xaxis.title !== undefined) && @@ -56,7 +50,9 @@ function initC2M(gd, defaultConfig) { yAxisText = gd._fullLayout.yaxis.title.text; } - + // I think that the traces have to point to their axis, + // since it might not be x1, could be x2, etc + // So this really needs to be part of process() var c2mData = {}; var types = []; var fullData = gd._fullData; From 15671b0687a3d50db39d790e199b67de4d4da863 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 1 Oct 2024 12:03:51 -0500 Subject: [PATCH 62/68] Schema update --- test/plot-schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plot-schema.json b/test/plot-schema.json index e17f5685239..dbe0927134e 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -356,7 +356,7 @@ "valType": "boolean" }, "sonification": { - "description": "Accessibility options: which library to use; whether to enable, options to pass to the library, info to pass to the library, closedCaptions to control how plotly renders the closed-captions element. chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ", + "description": "Sonification options: whether to enable, options to pass to the library, info to pass to the library, closedCaptions to control how plotly renders the closed-captions element. chart2music is supported and options here include Options and Info from https://www.chart2music.com/docs/API/Config. ", "dflt": { "closedCaptions": { "elClassname": "c2m-plotly-closed_captions", From 44f7ad39b16184dd73ebadb01a137bd42bde6bd3 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 1 Oct 2024 14:56:30 -0500 Subject: [PATCH 63/68] Remove accessibility wrapper directory --- src/accessibility/README.md | 3 --- src/plot_api/plot_api.js | 2 +- src/{accessibility => }/sonification/README.md | 0 src/{accessibility => }/sonification/all_codecs.js | 0 src/{accessibility => }/sonification/enable_sonification.js | 0 src/{accessibility => }/sonification/index.js | 0 src/{accessibility => }/sonification/xy_scatter_codec.js | 0 7 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 src/accessibility/README.md rename src/{accessibility => }/sonification/README.md (100%) rename src/{accessibility => }/sonification/all_codecs.js (100%) rename src/{accessibility => }/sonification/enable_sonification.js (100%) rename src/{accessibility => }/sonification/index.js (100%) rename src/{accessibility => }/sonification/xy_scatter_codec.js (100%) diff --git a/src/accessibility/README.md b/src/accessibility/README.md deleted file mode 100644 index f58a32e3e6a..00000000000 --- a/src/accessibility/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# accessibility - -While anticipating more accessibility libraries in the future, as of today this is a trivial wrapper over the `sonification` directory. diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 3ad45057585..1a24cf1922d 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -29,7 +29,7 @@ var manageArrays = require('./manage_arrays'); var helpers = require('./helpers'); var subroutines = require('./subroutines'); var editTypes = require('./edit_types'); -var sonification = require('../accessibility/sonification/enable_sonification'); +var sonification = require('../sonification/enable_sonification'); var AX_NAME_PATTERN = require('../plots/cartesian/constants').AX_NAME_PATTERN; diff --git a/src/accessibility/sonification/README.md b/src/sonification/README.md similarity index 100% rename from src/accessibility/sonification/README.md rename to src/sonification/README.md diff --git a/src/accessibility/sonification/all_codecs.js b/src/sonification/all_codecs.js similarity index 100% rename from src/accessibility/sonification/all_codecs.js rename to src/sonification/all_codecs.js diff --git a/src/accessibility/sonification/enable_sonification.js b/src/sonification/enable_sonification.js similarity index 100% rename from src/accessibility/sonification/enable_sonification.js rename to src/sonification/enable_sonification.js diff --git a/src/accessibility/sonification/index.js b/src/sonification/index.js similarity index 100% rename from src/accessibility/sonification/index.js rename to src/sonification/index.js diff --git a/src/accessibility/sonification/xy_scatter_codec.js b/src/sonification/xy_scatter_codec.js similarity index 100% rename from src/accessibility/sonification/xy_scatter_codec.js rename to src/sonification/xy_scatter_codec.js From 8e3b62b32ae54cd0599cf3d4df6938f5925b0288 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 1 Oct 2024 14:57:56 -0500 Subject: [PATCH 64/68] Fix filepaths --- src/sonification/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sonification/index.js b/src/sonification/index.js index c6a2cd02165..e1ab3995fbc 100644 --- a/src/sonification/index.js +++ b/src/sonification/index.js @@ -1,8 +1,8 @@ 'use strict'; var c2m = require('chart2music'); -var Fx = require('../../components/fx'); -var Lib = require('../../lib'); +var Fx = require('../components/fx'); +var Lib = require('../lib'); var codecs = require('./all_codecs').codecs; From aed1e0287822684503457e8f93ae52d84fb366cb Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 1 Oct 2024 15:19:55 -0500 Subject: [PATCH 65/68] Code cleanup --- src/sonification/index.js | 71 +++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/src/sonification/index.js b/src/sonification/index.js index e1ab3995fbc..bb53c6b570b 100644 --- a/src/sonification/index.js +++ b/src/sonification/index.js @@ -6,37 +6,47 @@ var Lib = require('../lib'); var codecs = require('./all_codecs').codecs; +/* initClosedCaptionDiv: Initialize the closed caption div with the given configuration. + * This function works by either creating a new div or returning the existing div +*/ +function initClosedCaptionDiv(gd, config) { + if(config.generate) { + var closedCaptions = document.createElement('div'); + closedCaptions.id = config.elId; + closedCaptions.className = config.elClassname; + gd.parentNode.insertBefore(closedCaptions, gd.nextSibling); // this really might not work + return closedCaptions; + } else { + return document.getElementById(config.elId); + } +} + +/* initC2M: Initialize the chart2music library with the given configuration. + * This function works by resetting the c2m context of the given graph div + */ function initC2M(gd, defaultConfig) { - // TODO what is there besides a fullReset? - // TODO Do we need the capacity to add data (live listen?) - // TODO Do we need the capacity to reset all data var c2mContext = gd._context._c2m = {}; c2mContext.options = Lib.extendDeepAll({}, defaultConfig.options); c2mContext.info = Lib.extendDeepAll({}, defaultConfig.info); c2mContext.ccOptions = Lib.extendDeepAll({}, defaultConfig.closedCaptions); - var labels = []; // TODO this probably needs to be stored in context- - // when data is updated this specific instance needs to be updated - // or the below function eneds to be reset to use the new instance of labels. + var labels = []; + // Set the onFocusCallback to highlight the hovered point c2mContext.options.onFocusCallback = function(dataInfo) { Fx.hover(gd, [{ curveNumber: labels.indexOf(dataInfo.slice), pointNumber: dataInfo.index }]); }; - // I generally don't like using closures like this. - // In this case it works out, we effectively treat 'labels' - // like a global, but it's a local. - // I'd rather the callback be given its c2m handler which - // could store extra data. Or bind c2mContext as `this`. + var ccElement = initClosedCaptionDiv(gd, c2mContext.ccOptions); + + // Get the chart, x, and y axis titles from the layout. + // This will be used for the closed captions. var titleText = 'Chart'; if((gd._fullLayout.title !== undefined) && (gd._fullLayout.title.text !== undefined)) { titleText = gd._fullLayout.title.text; } - - var ccElement = initClosedCaptionDiv(gd, c2mContext.ccOptions); - var xAxisText = 'X Axis'; if((gd._fullLayout.xaxis !== undefined) && (gd._fullLayout.xaxis.title !== undefined) && @@ -50,16 +60,14 @@ function initC2M(gd, defaultConfig) { yAxisText = gd._fullLayout.yaxis.title.text; } - // I think that the traces have to point to their axis, - // since it might not be x1, could be x2, etc - // So this really needs to be part of process() + // Convert the data to the format that c2m expects var c2mData = {}; var types = []; var fullData = gd._fullData; - // TODO: We're looping through all traces, and that's fine, but it might be helpful to discern how things are organized + + // Iterate through the traces and find the codec that matches the trace for(var i = 0; i < fullData.length; i++) { var trace = fullData[i]; - // TODO: what happens if this doesn't run, weird c2m errors for(var codecI = 0; codecI < codecs.length; codecI++) { var test = codecs[codecI].test(trace); if(!test) continue; @@ -76,18 +84,14 @@ function initC2M(gd, defaultConfig) { types.push(test.type); c2mData[label] = codecs[codecI].process(trace); } - } // TODO add unsupported codec + } c2mContext.c2mHandler = c2m.c2mChart({ title: titleText, type: types, axes: { - x: { // needs to be generated - label: xAxisText - }, - y: { - label: yAxisText - }, + x: { label: xAxisText }, + y: { label: yAxisText }, }, element: gd, cc: ccElement, @@ -95,20 +99,5 @@ function initC2M(gd, defaultConfig) { options: c2mContext.options, info: c2mContext.info }); - // TODO: We need to handle the possible error that c2mChart returns } exports.initC2M = initC2M; - -function initClosedCaptionDiv(gd, config) { - if(config.generate) { - var closedCaptions = document.createElement('div'); - closedCaptions.id = config.elId; - closedCaptions.className = config.elClassname; - gd.parentNode.insertBefore(closedCaptions, gd.nextSibling); // this really might not work - return closedCaptions; - } else { - return document.getElementById(config.elId); - } -} - -exports.initClosedCaptionDiv = initClosedCaptionDiv; From 1777fba5ca0725506d045c23abf618ab599b134e Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 1 Oct 2024 15:26:16 -0500 Subject: [PATCH 66/68] Code cleanup --- src/sonification/README.md | 4 ---- src/sonification/index.js | 12 ++++++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/sonification/README.md b/src/sonification/README.md index efa0dd20d47..77161f8d65f 100644 --- a/src/sonification/README.md +++ b/src/sonification/README.md @@ -17,10 +17,6 @@ The first three have most of their values set by the defaults in *src/plot_api/p * `initC2M(gd, defaultConfig)` full resets the `c2mChart` object. * `defaultConfig` is equal to `gd._context.sonification`, as defined in *src/plot_api/plot_config.js*. - -* `initClosedCaptionDiv(gd, config)` finds or creates the closed caption div, depending on `config`. - * `config` is equal to `defaultConfig.closedCaptions`. - ### all_codecs.js **all_codecs.js** agregates all the individual **_codec.js* files, all are expect to export exactly two functions: `test` and `process`. diff --git a/src/sonification/index.js b/src/sonification/index.js index bb53c6b570b..7257805290a 100644 --- a/src/sonification/index.js +++ b/src/sonification/index.js @@ -66,13 +66,13 @@ function initC2M(gd, defaultConfig) { var fullData = gd._fullData; // Iterate through the traces and find the codec that matches the trace - for(var i = 0; i < fullData.length; i++) { - var trace = fullData[i]; - for(var codecI = 0; codecI < codecs.length; codecI++) { - var test = codecs[codecI].test(trace); + for(var trace of fullData) { + for(var codec of codecs) { + var test = codec.test(trace); if(!test) continue; - var label = test.name ? test.name : i.toString() + ' '; + // Generate a unique label for the trace + var label = test.name ? test.name : i.toString() + ' '; var labelCount = 0; var originalLabel = label; while(label in c2mData) { @@ -82,7 +82,7 @@ function initC2M(gd, defaultConfig) { labels.push(label); types.push(test.type); - c2mData[label] = codecs[codecI].process(trace); + c2mData[label] = codec.process(trace); } } From 3a0384f72f3c4ec87ec03d041acfc0a1ad6ac428 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 1 Oct 2024 15:38:18 -0500 Subject: [PATCH 67/68] Code cleanup --- src/sonification/index.js | 10 +++++----- src/sonification/xy_scatter_codec.js | 8 +++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/sonification/index.js b/src/sonification/index.js index 7257805290a..ccdace37405 100644 --- a/src/sonification/index.js +++ b/src/sonification/index.js @@ -66,16 +66,16 @@ function initC2M(gd, defaultConfig) { var fullData = gd._fullData; // Iterate through the traces and find the codec that matches the trace - for(var trace of fullData) { - for(var codec of codecs) { + for (var trace of fullData) { + for (var codec of codecs) { var test = codec.test(trace); - if(!test) continue; + if (!test) continue; // Generate a unique label for the trace - var label = test.name ? test.name : i.toString() + ' '; + var label = test.name; var labelCount = 0; var originalLabel = label; - while(label in c2mData) { + for (label in c2mData) { labelCount++; label = originalLabel + labelCount.toString(); } diff --git a/src/sonification/xy_scatter_codec.js b/src/sonification/xy_scatter_codec.js index 88ad11c73ec..5fb5723a211 100644 --- a/src/sonification/xy_scatter_codec.js +++ b/src/sonification/xy_scatter_codec.js @@ -2,10 +2,8 @@ // This codec serves for one x axis and one y axis function test(trace) { - if(!trace) return null; - if(!trace.type || trace.type !== 'scatter') return null; - // TODO: I think we can have an x OR a y - if((trace.y === undefined || trace.y.length === 0) && (trace.x === undefined || trace.x.length === 0)) return null; + if (!trace || !trace.type || trace.type !== 'scatter') return null; + if ((trace.y === undefined || trace.y.length === 0) && (trace.x === undefined || trace.x.length === 0)) return null; return {type: 'scatter', name: trace.name}; } exports.test = test; @@ -16,7 +14,7 @@ function process(trace) { var y = trace.y && trace.y.length !== 0 ? trace.y : []; for(var p = 0; p < Math.max(x.length, y.length); p++) { - traceData.push({ // TODO I think we're copying the data here, bad. + traceData.push({ x: x[p] ? x[p] : p, y: y[p] ? y[p] : p, label: trace.text[p] ? trace.text[p] : p From dba0b53cdf0214967f2431b4ebd416d151e2ead9 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Fri, 4 Oct 2024 12:53:55 -0500 Subject: [PATCH 68/68] Fix trace bug --- src/sonification/index.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/sonification/index.js b/src/sonification/index.js index ccdace37405..97c371299dd 100644 --- a/src/sonification/index.js +++ b/src/sonification/index.js @@ -73,13 +73,6 @@ function initC2M(gd, defaultConfig) { // Generate a unique label for the trace var label = test.name; - var labelCount = 0; - var originalLabel = label; - for (label in c2mData) { - labelCount++; - label = originalLabel + labelCount.toString(); - } - labels.push(label); types.push(test.type); c2mData[label] = codec.process(trace);