maidr 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/.Rbuildignore +1 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.json +6 -0
  4. package/.github/workflows/build.yml +20 -0
  5. package/.prettierignore +3 -0
  6. package/.prettierrc.json +7 -0
  7. package/.vscode/extensions.json +25 -0
  8. package/.vscode/settings.json +30 -0
  9. package/.vscode/tasks.json +57 -0
  10. package/CHANGELOG.md +7 -0
  11. package/CITATION.cff +21 -0
  12. package/CONTRIBUTING.md +87 -0
  13. package/LICENSE.md +595 -0
  14. package/README.md +341 -0
  15. package/dist/maidr.js +8851 -0
  16. package/dist/maidr.min.js +1 -0
  17. package/dist/styles.css +244 -0
  18. package/dist/styles.min.css +1 -0
  19. package/docs/Audio.html +1398 -0
  20. package/docs/Constants.html +256 -0
  21. package/docs/Description.html +582 -0
  22. package/docs/Helper.html +364 -0
  23. package/docs/LogError.html +905 -0
  24. package/docs/Menu.html +665 -0
  25. package/docs/Position.html +174 -0
  26. package/docs/Resources.html +338 -0
  27. package/docs/Review.html +333 -0
  28. package/docs/Tracker.html +965 -0
  29. package/docs/audio.js.html +635 -0
  30. package/docs/constants.js.html +1242 -0
  31. package/docs/display.js.html +1184 -0
  32. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  33. package/docs/fonts/OpenSans-Bold-webfont.svg +1830 -0
  34. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  35. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  36. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  37. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  38. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  39. package/docs/fonts/OpenSans-Italic-webfont.svg +1830 -0
  40. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  41. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  42. package/docs/fonts/OpenSans-Light-webfont.svg +1831 -0
  43. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  44. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  45. package/docs/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  46. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  47. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  48. package/docs/fonts/OpenSans-Regular-webfont.svg +1831 -0
  49. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  50. package/docs/fonts/OpenSans-Semibold-webfont.eot +0 -0
  51. package/docs/fonts/OpenSans-Semibold-webfont.svg +1830 -0
  52. package/docs/fonts/OpenSans-Semibold-webfont.ttf +0 -0
  53. package/docs/fonts/OpenSans-Semibold-webfont.woff +0 -0
  54. package/docs/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
  55. package/docs/fonts/OpenSans-SemiboldItalic-webfont.svg +1830 -0
  56. package/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
  57. package/docs/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
  58. package/docs/index.html +66 -0
  59. package/docs/scripts/linenumber.js +25 -0
  60. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  61. package/docs/scripts/prettify/lang-css.js +2 -0
  62. package/docs/scripts/prettify/prettify.js +28 -0
  63. package/docs/styles/jsdoc-default.css +692 -0
  64. package/docs/styles/prettify-jsdoc.css +111 -0
  65. package/docs/styles/prettify-tomorrow.css +132 -0
  66. package/examples/dev_charts/barplot.html +1056 -0
  67. package/examples/dev_charts/boxplot.html +1856 -0
  68. package/examples/dev_charts/boxplot_flipped.svg +727 -0
  69. package/examples/dev_charts/heatmap.html +1217 -0
  70. package/examples/dev_charts/scatterplot/displ.js +18 -0
  71. package/examples/dev_charts/scatterplot/histogram_for_residual.svg +595 -0
  72. package/examples/dev_charts/scatterplot/hwy.js +15 -0
  73. package/examples/dev_charts/scatterplot/layers/point_layer.json +938 -0
  74. package/examples/dev_charts/scatterplot/layers/smooth_layer.json +322 -0
  75. package/examples/dev_charts/scatterplot/point_layer.js +938 -0
  76. package/examples/dev_charts/scatterplot/prediction_array.js +31 -0
  77. package/examples/dev_charts/scatterplot/prediction_array.json +31 -0
  78. package/examples/dev_charts/scatterplot/residual_array.js +29 -0
  79. package/examples/dev_charts/scatterplot/residual_array.json +29 -0
  80. package/examples/dev_charts/scatterplot/scatterplot.svg +1428 -0
  81. package/examples/dev_charts/scatterplot/scatterplot_data.html +2838 -0
  82. package/examples/dev_charts/scatterplot/scatterplot_no_jitter_point_only.svg +1393 -0
  83. package/examples/dev_charts/scatterplot/scatterplot_no_jitter_with_bestfit.svg +1424 -0
  84. package/examples/dev_charts/scatterplot/scatterplot_no_jitter_with_loess_curve.svg +1402 -0
  85. package/examples/dev_charts/scatterplot/smooth_layer.js +322 -0
  86. package/examples/dev_charts/scatterplot.html +4560 -0
  87. package/examples/dodged_bar/dodged_bar.png +0 -0
  88. package/examples/dodged_bar/dodged_bar.svg +198 -0
  89. package/examples/dodged_bar/schema.json +41 -0
  90. package/examples/histogram/histogram_tutorial.svg +482 -0
  91. package/examples/histogram/histogram_tutorial_raw_data.json +362 -0
  92. package/examples/histogram/histogram_user_study.svg +578 -0
  93. package/examples/histogram/histogram_user_study_raw_data.json +362 -0
  94. package/examples/lineplot/lineplot_sample.svg +126 -0
  95. package/examples/lineplot/lineplot_sample_raw_data.json +1 -0
  96. package/examples/lineplot/point+lineplot_sample.svg +700 -0
  97. package/examples/other/audio_oscillator_boxplot.js +95 -0
  98. package/examples/other/barplot_labels.svg +314 -0
  99. package/examples/other/barplot_user_study.svg +313 -0
  100. package/examples/other/boxplot.html +927 -0
  101. package/examples/other/boxplot_data_frame.html +568 -0
  102. package/examples/other/boxplot_label.svg +751 -0
  103. package/examples/other/braille-display_boxplot.js +79 -0
  104. package/examples/other/control_boxplot.js +55 -0
  105. package/examples/other/draft.js +56 -0
  106. package/examples/other/getData.html +400 -0
  107. package/examples/other/getData.js +41 -0
  108. package/examples/other/ggplot_to_svg.R +371 -0
  109. package/examples/other/heatmap.svg +582 -0
  110. package/examples/other/heatmap_label.svg +608 -0
  111. package/examples/other/multiple_barplot.html +2250 -0
  112. package/examples/other/new_scatterplot_user_study_point_layer.json +122 -0
  113. package/examples/other/py_binder_output.html +1167 -0
  114. package/examples/other/scatterplot_label.svg +1429 -0
  115. package/examples/other/seaborn_plot.py +9 -0
  116. package/examples/other/svglite_bar.svg +136 -0
  117. package/examples/other/tutorial_boxplot.svg +727 -0
  118. package/examples/other/tutorial_boxplot_data.json +72 -0
  119. package/examples/other/user_study_boxplot.svg +676 -0
  120. package/examples/stacked_bar/schema.json +41 -0
  121. package/examples/stacked_bar/stack_bar.png +0 -0
  122. package/examples/stacked_bar/stacked_bar.svg +180 -0
  123. package/examples/stacked_normalized_bar/stacked_normalized_bar.png +0 -0
  124. package/examples/stacked_normalized_bar/stacked_normalized_bar.svg +189 -0
  125. package/examples/static/barplot.svg +263 -0
  126. package/examples/static/barplot_diamonds_gridSVG.svg +254 -0
  127. package/examples/static/boxplot.svg +424 -0
  128. package/examples/static/heatmap.svg +373 -0
  129. package/examples/static/heatmap_penguins_table.html +486 -0
  130. package/examples/static/scatterplot.svg +530 -0
  131. package/examples/svglite/task_heatmap.html +802 -0
  132. package/examples/svglite/task_heatmap.svg +111 -0
  133. package/examples/svglite/tutorial_bar.svg +136 -0
  134. package/examples/svglite/tutorial_bar_plot.html +504 -0
  135. package/examples/svglite/tutorial_boxplot.html +1850 -0
  136. package/examples/svglite/tutorial_boxplot.svg +727 -0
  137. package/examples/svglite/tutorial_scatterplot.html +3135 -0
  138. package/examples/svglite/tutorial_scatterplot.svg +311 -0
  139. package/gulpfile.js +49 -0
  140. package/index.html +40 -0
  141. package/jsconfig.json +10 -0
  142. package/jsdoc.json +19 -0
  143. package/package.json +47 -0
  144. package/src/css/styles.css +241 -0
  145. package/src/js/__tests__/audio.test.js +49 -0
  146. package/src/js/__tests__/constants.test.js +622 -0
  147. package/src/js/audio.js +575 -0
  148. package/src/js/barplot.js +254 -0
  149. package/src/js/boxplot.js +682 -0
  150. package/src/js/constants.js +1182 -0
  151. package/src/js/controls.js +3182 -0
  152. package/src/js/display.js +1124 -0
  153. package/src/js/heatmap.js +411 -0
  154. package/src/js/histogram.js +134 -0
  155. package/src/js/init.js +427 -0
  156. package/src/js/lineplot.js +219 -0
  157. package/src/js/scatterplot.js +619 -0
  158. package/src/js/segmented.js +268 -0
  159. package/user_study_pilot/binder_test.html +526 -0
  160. package/user_study_pilot/data/barplot_user_study.svg +492 -0
  161. package/user_study_pilot/data/barplot_user_study_raw_data.json +22 -0
  162. package/user_study_pilot/data/boxplot_tutorial.json +72 -0
  163. package/user_study_pilot/data/boxplot_tutorial_horizontal.svg +727 -0
  164. package/user_study_pilot/data/boxplot_user_study.json +52 -0
  165. package/user_study_pilot/data/boxplot_user_study_vertical.svg +675 -0
  166. package/user_study_pilot/data/boxplot_user_study_vertical_horizontal.svg +676 -0
  167. package/user_study_pilot/data/heatmap_user_study.svg +719 -0
  168. package/user_study_pilot/data/heatmap_user_study_raw_data.json +127 -0
  169. package/user_study_pilot/data/new_barplot_user_study.svg +269 -0
  170. package/user_study_pilot/data/new_heatmap_user_study.svg +367 -0
  171. package/user_study_pilot/data/new_scatterplot_user_study.svg +603 -0
  172. package/user_study_pilot/data/new_scatterplot_user_study_point_layer.json +122 -0
  173. package/user_study_pilot/data/scatterplot_user_study (1).svg +321 -0
  174. package/user_study_pilot/data/scatterplot_user_study.svg +603 -0
  175. package/user_study_pilot/data/scatterplot_user_study_point_layer.json +122 -0
  176. package/user_study_pilot/data/scatterplot_user_study_smooth_layer.json +322 -0
  177. package/user_study_pilot/intro.html +215 -0
  178. package/user_study_pilot/jaws_settings/Chrome.JDF +10 -0
  179. package/user_study_pilot/jaws_settings/Firefox.JDF +10 -0
  180. package/user_study_pilot/jaws_settings/backup_utf8/Chrome.JDF +10 -0
  181. package/user_study_pilot/jaws_settings/backup_utf8/Firefox.JDF +10 -0
  182. package/user_study_pilot/jaws_settings/backup_utf8/msedge.JDF +10 -0
  183. package/user_study_pilot/jaws_settings/msedge.JDF +10 -0
  184. package/user_study_pilot/nvda_settings/chrome.dic +10 -0
  185. package/user_study_pilot/nvda_settings/default.dic +10 -0
  186. package/user_study_pilot/nvda_settings/firefox.dic +10 -0
  187. package/user_study_pilot/nvda_settings/msedge.dic +10 -0
  188. package/user_study_pilot/scatterplot.html +4560 -0
  189. package/user_study_pilot/seaborn_test.html +1059 -0
  190. package/user_study_pilot/svglite_test.html +534 -0
  191. package/user_study_pilot/task1_bar_plot.html +1111 -0
  192. package/user_study_pilot/task2_heatmap.html +1661 -0
  193. package/user_study_pilot/task3_boxplot_horizontal.html +1690 -0
  194. package/user_study_pilot/task3_boxplot_vertical.html +1689 -0
  195. package/user_study_pilot/task4_scatterplot.html +2091 -0
  196. package/user_study_pilot/tutorial1_bar_plot.html +1159 -0
  197. package/user_study_pilot/tutorial2_heatmap.html +1276 -0
  198. package/user_study_pilot/tutorial3_boxplot_horizontal.html +1861 -0
  199. package/user_study_pilot/tutorial3_boxplot_vertical.html +1807 -0
  200. package/user_study_pilot/tutorial4_scatterplot.html +5893 -0
  201. package/user_study_pilot/tutorial5_histogram.html +1553 -0
  202. package/user_study_pilot/tutorial6_lineplot.html +1011 -0
  203. package/user_study_pilot/tutorial7_stacked.html +763 -0
  204. package/user_study_pilot/tutorial8_stacked_normalized.html +796 -0
  205. package/user_study_pilot/tutorial9_dodged_bar.html +831 -0
  206. package/user_study_pilot/voiceover_settings/user_study_VoiceOver Archive.voprefs +573 -0
@@ -0,0 +1,1182 @@
1
+ /**
2
+ * A class representing constants used throughout the application.
3
+ */
4
+ class Constants {
5
+ // element ids
6
+ /**
7
+ * The ID of the chart container element.
8
+ * @type {string}
9
+ */
10
+ chart_container_id = 'chart-container';
11
+ main_container_id = 'maidr-container';
12
+ //chart_container_class = 'chart-container'; // remove later
13
+ braille_container_id = 'braille-div';
14
+ braille_input_id = 'braille-input';
15
+ info_id = 'info';
16
+ announcement_container_id = 'announcements';
17
+ end_chime_id = 'end_chime';
18
+ container_id = 'container';
19
+ project_id = 'maidr';
20
+ review_id_container = 'review_container';
21
+ review_id = 'review';
22
+ reviewSaveSpot;
23
+ reviewSaveBrailleMode;
24
+ chartId = '';
25
+ events = [];
26
+ postLoadEvents = [];
27
+
28
+ // default constructor for all charts
29
+ /**
30
+ * Creates a new instance of the Constants class.
31
+ * @constructor
32
+ */
33
+ constructor() {}
34
+
35
+ // BTS modes initial values
36
+ textMode = 'verbose'; // off / terse / verbose
37
+ brailleMode = 'off'; // on / off
38
+ sonifMode = 'on'; // sep / same / off
39
+ reviewMode = 'off'; // on / off
40
+
41
+ // basic chart properties
42
+ minX = 0;
43
+ maxX = 0;
44
+ minY = 0;
45
+ maxY = 0;
46
+ plotId = ''; // update with id in chart specific js
47
+ chartType = ''; // set as 'box' or whatever later in chart specific js file
48
+ navigation = 1; // 0 = row navigation (up/down), 1 = col navigation (left/right)
49
+
50
+ // basic audio properties
51
+ MAX_FREQUENCY = 1000;
52
+ MIN_FREQUENCY = 200;
53
+ NULL_FREQUENCY = 100;
54
+
55
+ // autoplay speed
56
+ MAX_SPEED = 500;
57
+ MIN_SPEED = 50; // 50;
58
+ DEFAULT_SPEED = 250;
59
+ INTERVAL = 20;
60
+ AUTOPLAY_DURATION = 5000; // 5s
61
+
62
+ // user settings
63
+ vol = 0.5;
64
+ MAX_VOL = 30;
65
+ // autoPlayRate = this.DEFAULT_SPEED; // ms per tone
66
+ autoPlayRate = this.DEFAULT_SPEED; // ms per tone
67
+ colorSelected = '#03C809';
68
+ brailleDisplayLength = 32; // num characters in user's braille display. 40 is common length for desktop / mobile applications
69
+
70
+ // advanced user settings
71
+ showRect = 1; // true / false
72
+ hasRect = 1; // true / false
73
+ hasSmooth = 1; // true / false (for smooth line points)
74
+ duration = 0.3;
75
+ outlierDuration = 0.06;
76
+ autoPlayOutlierRate = 50; // ms per tone
77
+ autoPlayPointsRate = 50; // time between tones in a run
78
+ colorUnselected = '#595959'; // deprecated, todo: find all instances replace with storing old color method
79
+ isTracking = 1; // 0 / 1, is tracking on or off
80
+ visualBraille = false; // do we want to represent braille based on what's visually there or actually there. Like if we have 2 outliers with the same position, do we show 1 (visualBraille true) or 2 (false)
81
+ globalMinMax = true;
82
+
83
+ // user controls (not exposed to menu, with shortcuts usually)
84
+ showDisplay = 1; // true / false
85
+ showDisplayInBraille = 1; // true / false
86
+ showDisplayInAutoplay = 0; // true / false
87
+ outlierInterval = null;
88
+
89
+ // platform controls
90
+ isMac = navigator.userAgent.toLowerCase().includes('mac'); // true if macOS
91
+ control = this.isMac ? 'Cmd' : 'Ctrl';
92
+ alt = this.isMac ? 'option' : 'Alt';
93
+ home = this.isMac ? 'fn + Left arrow' : 'Home';
94
+ end = this.isMac ? 'fn + Right arrow' : 'End';
95
+
96
+ // internal controls
97
+ keypressInterval = 2000; // ms or 2s
98
+ tabMovement = null;
99
+
100
+ // debug stuff
101
+ debugLevel = 3; // 0 = no console output, 1 = some console, 2 = more console, etc
102
+ canPlayEndChime = false; //
103
+ manualData = true; // pull from manual data like chart2music (true), or do the old method where we pull from the chart (false)
104
+
105
+ KillAutoplay() {
106
+ if (this.autoplayId) {
107
+ clearInterval(this.autoplayId);
108
+ this.autoplayId = null;
109
+ }
110
+ }
111
+
112
+ KillSepPlay() {
113
+ if (this.sepPlayId) {
114
+ clearInterval(this.sepPlayId);
115
+ this.sepPlayId = null;
116
+ }
117
+ }
118
+
119
+ SpeedUp() {
120
+ if (constants.autoPlayRate - this.INTERVAL > this.MIN_SPEED) {
121
+ constants.autoPlayRate -= this.INTERVAL;
122
+ }
123
+ }
124
+
125
+ SpeedDown() {
126
+ if (constants.autoPlayRate + this.INTERVAL <= this.MAX_SPEED) {
127
+ constants.autoPlayRate += this.INTERVAL;
128
+ }
129
+ }
130
+
131
+ SpeedReset() {
132
+ constants.autoPlayRate = constants.DEFAULT_SPEED;
133
+ }
134
+
135
+ ColorInvert(color) {
136
+ // invert an rgb color
137
+ let rgb = color.replace(/[^\d,]/g, '').split(',');
138
+ let r = 255 - rgb[0];
139
+ let g = 255 - rgb[1];
140
+ let b = 255 - rgb[2];
141
+ return 'rgb(' + r + ',' + g + ',' + b + ')';
142
+ }
143
+ GetBetterColor(oldColor) {
144
+ // get a highly contrasting color against the current
145
+ // method: choose an inverted color, but if it's just a shade of gray, default to this.colorSelected
146
+ let newColor = this.ColorInvert(oldColor);
147
+ let rgb = newColor.replace(/[^\d,]/g, '').split(',');
148
+ if (
149
+ rgb[1] < rgb[0] + 10 &&
150
+ rgb[1] > rgb[0] - 10 &&
151
+ rgb[2] < rgb[0] + 10 &&
152
+ rgb[2] > rgb[0] - 10 &&
153
+ (rgb[0] > 86 || rgb[0] < 169)
154
+ ) {
155
+ // too gray and too close to center gray, use default
156
+ newColor = this.colorSelected;
157
+ }
158
+
159
+ return newColor;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Resources class contains properties and methods related to language, knowledge level, and strings.
165
+ */
166
+ class Resources {
167
+ constructor() {}
168
+
169
+ language = 'en'; // 2 char lang code
170
+ knowledgeLevel = 'basic'; // basic, intermediate, expert
171
+
172
+ // these strings run on getters, which pull in language, knowledgeLevel, chart, and actual requested string
173
+ strings = {
174
+ en: {
175
+ basic: {
176
+ upper_outlier: 'Upper Outlier',
177
+ lower_outlier: 'Lower Outlier',
178
+ min: 'Minimum',
179
+ max: 'Maximum',
180
+ 25: '25%',
181
+ 50: '50%',
182
+ 75: '75%',
183
+ q1: '25%',
184
+ q2: '50%',
185
+ q3: '75%',
186
+ son_on: 'Sonification on',
187
+ son_off: 'Sonification off',
188
+ son_des: 'Sonification descrete',
189
+ son_comp: 'Sonification compare',
190
+ son_ch: 'Sonification chord',
191
+ son_sep: 'Sonification separate',
192
+ son_same: 'Sonification combined',
193
+ empty: 'Empty',
194
+ },
195
+ },
196
+ };
197
+
198
+ /**
199
+ * Returns a string based on the provided ID, language, and knowledge level.
200
+ * @param {string} id - The ID of the string to retrieve.
201
+ * @returns {string} The string corresponding to the provided ID, language, and knowledge level.
202
+ */
203
+ GetString(id) {
204
+ return this.strings[this.language][this.knowledgeLevel][id];
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Represents a menu object with various settings and keyboard shortcuts.
210
+ */
211
+ class Menu {
212
+ whereWasMyFocus = null;
213
+
214
+ constructor() {
215
+ this.CreateMenu();
216
+ this.LoadDataFromLocalStorage();
217
+ }
218
+
219
+ menuHtml = `
220
+ <div id="menu" class="modal hidden" role="dialog" tabindex="-1">
221
+ <div class="modal-dialog" role="document" tabindex="0">
222
+ <div class="modal-content">
223
+ <div class="modal-header">
224
+ <h4 class="modal-title">Menu</h4>
225
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
226
+ <span aria-hidden="true">&times;</span>
227
+ </button>
228
+ </div>
229
+ <div class="modal-body">
230
+ <div>
231
+ <h5 class="modal-title">Keyboard Shortcuts</h5>
232
+ <table>
233
+ <caption class="sr-only">Keyboard Shortcuts</caption>
234
+ <thead>
235
+ <tr>
236
+ <th scope="col">Function</th>
237
+ <th scope="col">Key</th>
238
+ </tr>
239
+ </thead>
240
+ <tbody>
241
+ <tr>
242
+ <td>Move around plot</td>
243
+ <td>Arrow keys</td>
244
+ </tr>
245
+ <tr>
246
+ <td>Go to the very left right up down</td>
247
+ <td>${constants.control} + Arrow key</td>
248
+ </tr>
249
+ <tr>
250
+ <td>Select the first element</td>
251
+ <td>${constants.control} + ${constants.home}</td>
252
+ </tr>
253
+ <tr>
254
+ <td>Select the last element</td>
255
+ <td>${constants.control} + ${constants.end}</td>
256
+ </tr>
257
+ <tr>
258
+ <td>Toggle Braille Mode</td>
259
+ <td>b</td>
260
+ </tr>
261
+ <tr>
262
+ <td>Toggle Sonification Mode</td>
263
+ <td>s</td>
264
+ </tr>
265
+ <tr>
266
+ <td>Toggle Text Mode</td>
267
+ <td>t</td>
268
+ </tr>
269
+ <tr>
270
+ <td>Repeat current sound</td>
271
+ <td>Space</td>
272
+ </tr>
273
+ <tr>
274
+ <td>Auto-play outward in direction of arrow</td>
275
+ <td>${constants.control} + Shift + Arrow key</td>
276
+ </tr>
277
+ <tr>
278
+ <td>Auto-play inward in direction of arrow</td>
279
+ <td>${constants.alt} + Shift + Arrow key</td>
280
+ </tr>
281
+ <tr>
282
+ <td>Stop Auto-play</td>
283
+ <td>${constants.control}</td>
284
+ </tr>
285
+ <tr>
286
+ <td>Auto-play speed up</td>
287
+ <td>Period</td>
288
+ </tr>
289
+ <tr>
290
+ <td>Auto-play speed down</td>
291
+ <td>Comma</td>
292
+ </tr>
293
+ </tbody>
294
+ </table>
295
+ </div>
296
+
297
+ <div>
298
+ <h5 class="modal-title">Settings</h5>
299
+ <p><input type="range" id="vol" name="vol" min="0" max="1" step=".05"><label for="vol">Volume</label></p>
300
+ <!-- <p><input type="checkbox" id="show_rect" name="show_rect"><label for="show_rect">Show Outline</label></p> //-->
301
+ <p><input type="number" min="4" max="2000" step="1" id="braille_display_length" name="braille_display_length"><label for="braille_display_length">Braille Display Size</label></p>
302
+ <p><input type="number" min="${constants.MIN_SPEED}" max="500" step="${constants.INTERVAL}" id="autoplay_rate" name="autoplay_rate"><label for="autoplay_rate">Autoplay Rate</label></p>
303
+ <p><input type="color" id="color_selected" name="color_selected"><label for="color_selected">Outline Color</label></p>
304
+ <p><input type="number" min="10" max="2000" step="10" id="min_freq" name="min_freq"><label for="min_freq">Min Frequency (Hz)</label></p>
305
+ <p><input type="number" min="20" max="2010" step="10" id="max_freq" name="max_freq"><label for="max_freq">Max Frequency (Hz)</label></p>
306
+ <p><input type="number" min="500" max="5000" step="500" id="keypress_interval" name="keypress_interval"><label for="keypress_interval">Keypress Interval (ms)</label></p>
307
+ </div>
308
+ </div>
309
+ <div class="modal-footer">
310
+ <button type="button" id="save_and_close_menu">Save and close</button>
311
+ <button type="button" id="close_menu">Close</button>
312
+ </div>
313
+ </div>
314
+ </div>
315
+ </div>
316
+ <div id="menu_modal_backdrop" class="modal-backdrop hidden"></div>
317
+ `;
318
+
319
+ /**
320
+ * Creates a menu element and sets up event listeners for opening and closing the menu.
321
+ */
322
+ CreateMenu() {
323
+ // menu element creation
324
+ document
325
+ .querySelector('body')
326
+ .insertAdjacentHTML('beforeend', this.menuHtml);
327
+
328
+ // menu close events
329
+ let allClose = document.querySelectorAll('#close_menu, #menu .close');
330
+ for (let i = 0; i < allClose.length; i++) {
331
+ constants.events.push([
332
+ allClose[i],
333
+ 'click',
334
+ function (e) {
335
+ menu.Toggle(false);
336
+ },
337
+ ]);
338
+ }
339
+ constants.events.push([
340
+ document.getElementById('save_and_close_menu'),
341
+ 'click',
342
+ function (e) {
343
+ menu.SaveData();
344
+ menu.Toggle(false);
345
+ },
346
+ ]);
347
+ constants.events.push([
348
+ document.getElementById('menu'),
349
+ 'keydown',
350
+ function (e) {
351
+ if (e.key == 'Esc') {
352
+ // esc
353
+ menu.Toggle(false);
354
+ }
355
+ },
356
+ ]);
357
+
358
+ // open events
359
+ // note: this triggers a maidr destroy
360
+ constants.events.push([
361
+ document,
362
+ 'keyup',
363
+ function (e) {
364
+ if (e.key == 'h') {
365
+ menu.Toggle(true);
366
+ }
367
+ },
368
+ ]);
369
+ }
370
+
371
+ /**
372
+ * Destroys the menu element and its backdrop.
373
+ * @function
374
+ * @name Destroy
375
+ * @memberof module:constants
376
+ * @returns {void}
377
+ */
378
+ Destroy() {
379
+ // menu element destruction
380
+ let menu = document.getElementById('menu');
381
+ if (menu) {
382
+ menu.remove();
383
+ }
384
+ let backdrop = document.getElementById('menu_modal_backdrop');
385
+ if (backdrop) {
386
+ backdrop.remove();
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Toggles the menu on and off.
392
+ * @param {boolean} [onoff=false] - Whether to turn the menu on or off. Defaults to false.
393
+ */
394
+ Toggle(onoff = false) {
395
+ if (typeof onoff == 'undefined') {
396
+ if (document.getElementById('menu').classList.contains('hidden')) {
397
+ onoff = true;
398
+ } else {
399
+ onoff = false;
400
+ }
401
+ }
402
+ if (onoff) {
403
+ // open
404
+ this.whereWasMyFocus = document.activeElement;
405
+ this.PopulateData();
406
+ constants.tabMovement = 0;
407
+ document.getElementById('menu').classList.remove('hidden');
408
+ document.getElementById('menu_modal_backdrop').classList.remove('hidden');
409
+ document.querySelector('#menu .close').focus();
410
+ } else {
411
+ // close
412
+ document.getElementById('menu').classList.add('hidden');
413
+ document.getElementById('menu_modal_backdrop').classList.add('hidden');
414
+ this.whereWasMyFocus.focus();
415
+ this.whereWasMyFocus = null;
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Populates the data in the HTML elements with the values from the constants object.
421
+ */
422
+ PopulateData() {
423
+ document.getElementById('vol').value = constants.vol;
424
+ //document.getElementById('show_rect').checked = constants.showRect;
425
+ document.getElementById('autoplay_rate').value = constants.autoPlayRate;
426
+ document.getElementById('braille_display_length').value =
427
+ constants.brailleDisplayLength;
428
+ document.getElementById('color_selected').value = constants.colorSelected;
429
+ document.getElementById('min_freq').value = constants.MIN_FREQUENCY;
430
+ document.getElementById('max_freq').value = constants.MAX_FREQUENCY;
431
+ document.getElementById('keypress_interval').value =
432
+ constants.keypressInterval;
433
+ }
434
+
435
+ /**
436
+ * Saves the data from the HTML elements into the constants object.
437
+ */
438
+ SaveData() {
439
+ constants.vol = document.getElementById('vol').value;
440
+ //constants.showRect = document.getElementById('show_rect').checked;
441
+ constants.autoPlayRate = document.getElementById('autoplay_rate').value;
442
+ constants.brailleDisplayLength = document.getElementById(
443
+ 'braille_display_length'
444
+ ).value;
445
+ constants.colorSelected = document.getElementById('color_selected').value;
446
+ constants.MIN_FREQUENCY = document.getElementById('min_freq').value;
447
+ constants.MAX_FREQUENCY = document.getElementById('max_freq').value;
448
+ constants.keypressInterval =
449
+ document.getElementById('keypress_interval').value;
450
+ }
451
+
452
+ /**
453
+ * Saves all data in this.SaveData() to local storage.
454
+ * @function
455
+ * @memberof constants
456
+ * @returns {void}
457
+ */
458
+ SaveDataToLocalStorage() {
459
+ // save all data in this.SaveData() to local storage
460
+ let data = {};
461
+ data.vol = constants.vol;
462
+ //data.showRect = constants.showRect;
463
+ data.autoPlayRate = constants.autoPlayRate;
464
+ data.brailleDisplayLength = constants.brailleDisplayLength;
465
+ data.colorSelected = constants.colorSelected;
466
+ data.MIN_FREQUENCY = constants.MIN_FREQUENCY;
467
+ data.MAX_FREQUENCY = constants.MAX_FREQUENCY;
468
+ data.keypressInterval = constants.keypressInterval;
469
+ localStorage.setItem('settings_data', JSON.stringify(data));
470
+ }
471
+ /**
472
+ * Loads data from local storage and updates the constants object with the retrieved values.
473
+ */
474
+ LoadDataFromLocalStorage() {
475
+ let data = JSON.parse(localStorage.getItem('settings_data'));
476
+ if (data) {
477
+ constants.vol = data.vol;
478
+ //constants.showRect = data.showRect;
479
+ constants.autoPlayRate = data.autoPlayRate;
480
+ constants.brailleDisplayLength = data.brailleDisplayLength;
481
+ constants.colorSelected = data.colorSelected;
482
+ constants.MIN_FREQUENCY = data.MIN_FREQUENCY;
483
+ constants.MAX_FREQUENCY = data.MAX_FREQUENCY;
484
+ constants.keypressInterval = data.keypressInterval;
485
+ }
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Creates an html modal containing summary info of the active chart.
491
+ * @class
492
+ */
493
+ class Description {
494
+ // This class creates an html modal containing summary info of the active chart
495
+ // Trigger popup with 'D' key
496
+ // Info is basically anything available, but stuff like:
497
+ // - chart type
498
+ // - chart labels, like title, subtitle, caption etc
499
+ // - chart data (an accessible html table)
500
+
501
+ constructor() {
502
+ //this.CreateComponent(); // disabled as we're in development and have switched priorities
503
+ }
504
+
505
+ /**
506
+ * Creates a modal component containing description summary stuff.
507
+ */
508
+ CreateComponent() {
509
+ // modal containing description summary stuff
510
+ let html = `
511
+ <div id="description" class="modal hidden" role="dialog" tabindex="-1">
512
+ <div class="modal-dialog" role="document" tabindex="0">
513
+ <div class="modal-content">
514
+ <div class="modal-header">
515
+ <h4 id="desc_title" class="modal-title">Description</h4>
516
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
517
+ <span aria-hidden="true">&times;</span>
518
+ </button>
519
+ </div>
520
+ <div class="modal-body">
521
+ <div id="desc_content">
522
+ content here
523
+ </div>
524
+ <div id="desc_table">
525
+ </div>
526
+ </div>
527
+ <div class="modal-footer">
528
+ <button type="button" id="close_desc">Close</button>
529
+ </div>
530
+ </div>
531
+ </div>
532
+ </div>
533
+ <div id="desc_modal_backdrop" class="modal-backdrop hidden"></div>
534
+
535
+ `;
536
+
537
+ document.querySelector('body').insertAdjacentHTML('beforeend', html);
538
+
539
+ // close events
540
+ let allClose = document.querySelectorAll(
541
+ '#close_desc, #description .close'
542
+ );
543
+ for (let i = 0; i < allClose.length; i++) {
544
+ constants.events.push([
545
+ allClose[i],
546
+ 'click',
547
+ function (e) {
548
+ description.Toggle(false);
549
+ },
550
+ ]);
551
+ }
552
+ constants.events.push([
553
+ document.getElementById('description'),
554
+ 'keydown',
555
+ function (e) {
556
+ if (e.key == 'Esc') {
557
+ // esc
558
+ description.Toggle(false);
559
+ }
560
+ },
561
+ ]);
562
+
563
+ // open events
564
+ constants.events.push([
565
+ document,
566
+ 'keyup',
567
+ function (e) {
568
+ if (e.key == 'd') {
569
+ description.Toggle(true);
570
+ }
571
+ },
572
+ ]);
573
+ }
574
+
575
+ /**
576
+ * Removes the description element and backdrop from the DOM.
577
+ */
578
+ Destroy() {
579
+ // description element destruction
580
+ let description = document.getElementById('menu');
581
+ if (description) {
582
+ description.remove();
583
+ }
584
+ let backdrop = document.getElementById('desc_modal_backdrop');
585
+ if (backdrop) {
586
+ backdrop.remove();
587
+ }
588
+ }
589
+
590
+ /**
591
+ * Toggles the visibility of the description element.
592
+ * @param {boolean} [onoff=false] - Whether to turn the description element on or off.
593
+ */
594
+ Toggle(onoff = false) {
595
+ if (typeof onoff == 'undefined') {
596
+ if (document.getElementById('description').classList.contains('hidden')) {
597
+ onoff = true;
598
+ } else {
599
+ onoff = false;
600
+ }
601
+ }
602
+ if (onoff) {
603
+ // open
604
+ this.whereWasMyFocus = document.activeElement;
605
+ constants.tabMovement = 0;
606
+ this.PopulateData();
607
+ document.getElementById('description').classList.remove('hidden');
608
+ document.getElementById('desc_modal_backdrop').classList.remove('hidden');
609
+ document.querySelector('#description .close').focus();
610
+ } else {
611
+ // close
612
+ document.getElementById('description').classList.add('hidden');
613
+ document.getElementById('desc_modal_backdrop').classList.add('hidden');
614
+ this.whereWasMyFocus.focus();
615
+ this.whereWasMyFocus = null;
616
+ }
617
+ }
618
+
619
+ /**
620
+ * Populates the data for the chart and table based on the chart type and plot data.
621
+ */
622
+ PopulateData() {
623
+ let descHtml = '';
624
+
625
+ // chart labels and descriptions
626
+ let descType = '';
627
+ if (constants.chartType == 'bar') {
628
+ descType = 'Bar chart';
629
+ } else if (constants.chartType == 'heat') {
630
+ descType = 'Heatmap';
631
+ } else if (constants.chartType == 'box') {
632
+ descType = 'Box plot';
633
+ } else if (constants.chartType == 'scatter') {
634
+ descType = 'Scatter plot';
635
+ } else if (constants.chartType == 'line') {
636
+ descType = 'Line chart';
637
+ } else if (constants.chartType == 'hist') {
638
+ descType = 'Histogram';
639
+ }
640
+
641
+ if (descType) {
642
+ descHtml += `<p>Type: ${descType}</p>`;
643
+ }
644
+ if (plot.title != null) {
645
+ descHtml += `<p>Title: ${plot.title}</p>`;
646
+ }
647
+ if (plot.subtitle != null) {
648
+ descHtml += `<p>Subtitle: ${plot.subtitle}</p>`;
649
+ }
650
+ if (plot.caption != null) {
651
+ descHtml += `<p>Caption: ${plot.caption}</p>`;
652
+ }
653
+
654
+ // table of data, prep
655
+ let descTableHtml = '';
656
+ let descLabelX = null;
657
+ let descLabelY = null;
658
+ let descTickX = null;
659
+ let descTickY = null;
660
+ let descData = null;
661
+ let descNumCols = 0;
662
+ let descNumColsWithLabels = 0;
663
+ let descNumRows = 0;
664
+ let descNumRowsWithLabels = 0;
665
+ if (constants.chartType == 'bar') {
666
+ if (plot.plotLegend.x != null) {
667
+ descLabelX = plot.plotLegend.x;
668
+ descNumColsWithLabels += 1;
669
+ }
670
+ if (plot.plotLegend.y != null) {
671
+ descLabelY = plot.plotLegend.y;
672
+ descNumRowsWithLabels += 1;
673
+ }
674
+ if (plot.columnLabels != null) {
675
+ descTickX = plot.columnLabels;
676
+ descNumRowsWithLabels += 1;
677
+ }
678
+ if (plot.plotData != null) {
679
+ descData = [];
680
+ descData[0] = plot.plotData;
681
+ descNumCols = plot.plotData.length;
682
+ descNumRows = 1;
683
+ descNumColsWithLabels += descNumCols;
684
+ descNumRowsWithLabels += descNumRows;
685
+ }
686
+ }
687
+
688
+ // table of data, create
689
+ if (descData != null) {
690
+ descTableHtml += '<table>';
691
+
692
+ // header rows
693
+ if (descLabelX != null || descTickX != null) {
694
+ descTableHtml += '<thead>';
695
+ if (descLabelX != null) {
696
+ descTableHtml += '<tr>';
697
+ if (descLabelY != null) {
698
+ descTableHtml += '<td></td>';
699
+ }
700
+ if (descTickY != null) {
701
+ descTableHtml += '<td></td>';
702
+ }
703
+ descTableHtml += `<th scope="col" colspan="${descNumCols}">${descLabelX}</th>`;
704
+ descTableHtml += '</tr>';
705
+ }
706
+ if (descTickX != null) {
707
+ descTableHtml += '<tr>';
708
+ if (descLabelY != null) {
709
+ descTableHtml += '<td></td>';
710
+ }
711
+ if (descTickY != null) {
712
+ descTableHtml += '<td></td>';
713
+ }
714
+ for (let i = 0; i < descNumCols; i++) {
715
+ descTableHtml += `<th scope="col">${descTickX[i]}</th>`;
716
+ }
717
+ descTableHtml += '</tr>';
718
+ }
719
+ descTableHtml += '</thead>';
720
+ }
721
+
722
+ // body rows
723
+ if (descNumRows > 0) {
724
+ descTableHtml += '<tbody>';
725
+ for (let i = 0; i < descNumRows; i++) {
726
+ descTableHtml += '<tr>';
727
+ if (descLabelY != null && i == 0) {
728
+ descTableHtml += `<th scope="row" rowspan="${descNumRows}">${descLabelY}</th>`;
729
+ }
730
+ if (descTickY != null) {
731
+ descTableHtml += `<th scope="row">${descTickY[i]}</th>`;
732
+ }
733
+ for (let j = 0; j < descNumCols; j++) {
734
+ descTableHtml += `<td>${descData[i][j]}</td>`;
735
+ }
736
+ descTableHtml += '</tr>';
737
+ }
738
+ descTableHtml += '</tbody>';
739
+ }
740
+
741
+ descTableHtml += '</table>';
742
+ }
743
+
744
+ // bar: don't need colspan or rowspan stuff, put legendX and Y as headers
745
+
746
+ document.getElementById('desc_title').innerHTML = descType + ' description';
747
+ document.getElementById('desc_content').innerHTML = descHtml;
748
+ document.getElementById('desc_table').innerHTML = descTableHtml;
749
+ }
750
+ }
751
+
752
+ /**
753
+ * Represents a position in 3D space.
754
+ * @class
755
+ */
756
+ class Position {
757
+ constructor(x, y, z = -1) {
758
+ this.x = x;
759
+ this.y = y;
760
+ this.z = z; // rarely used
761
+ }
762
+ }
763
+
764
+ // HELPER FUNCTIONS
765
+ /**
766
+ * A helper class with static methods.
767
+ */
768
+ class Helper {
769
+ /**
770
+ * Checks if an object is present in an array.
771
+ * @param {Object} obj - The object to search for.
772
+ * @param {Array} arr - The array to search in.
773
+ * @returns {boolean} - True if the object is present in the array, false otherwise.
774
+ */
775
+ static containsObject(obj, arr) {
776
+ for (let i = 0; i < arr.length; i++) {
777
+ if (arr[i] === obj) return true;
778
+ }
779
+ return false;
780
+ }
781
+ }
782
+
783
+ /**
784
+ * A class representing a Tracker.
785
+ * @class
786
+ */
787
+ class Tracker {
788
+ constructor() {
789
+ this.DataSetup();
790
+ }
791
+
792
+ /**
793
+ * Sets up the tracker data by checking if previous data exists and creating new data if it doesn't.
794
+ */
795
+ DataSetup() {
796
+ let prevData = this.GetTrackerData();
797
+ if (prevData) {
798
+ // good to go already, do nothing
799
+ } else {
800
+ let data = {};
801
+ data.userAgent = Object.assign(navigator.userAgent);
802
+ data.vendor = Object.assign(navigator.vendor);
803
+ data.language = Object.assign(navigator.language);
804
+ data.platform = Object.assign(navigator.platform);
805
+ data.events = [];
806
+
807
+ this.SaveTrackerData(data);
808
+ }
809
+ }
810
+
811
+ /**
812
+ * Downloads the tracker data as a JSON file.
813
+ */
814
+ DownloadTrackerData() {
815
+ let link = document.createElement('a');
816
+ let data = this.GetTrackerData();
817
+ let fileStr = new Blob([JSON.stringify(data)], { type: 'text/plain' });
818
+ link.href = URL.createObjectURL(fileStr);
819
+ link.download = 'tracking.json';
820
+ link.click();
821
+ }
822
+
823
+ /**
824
+ * Saves the tracker data to local storage.
825
+ * @param {Object} data - The data to be saved.
826
+ */
827
+ SaveTrackerData(data) {
828
+ localStorage.setItem(constants.project_id, JSON.stringify(data));
829
+ }
830
+
831
+ /**
832
+ * Retrieves tracker data from local storage.
833
+ * @returns {Object} The tracker data.
834
+ */
835
+ GetTrackerData() {
836
+ let data = JSON.parse(localStorage.getItem(constants.project_id));
837
+ return data;
838
+ }
839
+
840
+ /**
841
+ * Removes the project_id from localStorage, clears the tracking data, and sets up new data.
842
+ */
843
+ Delete() {
844
+ localStorage.removeItem(constants.project_id);
845
+ this.data = null;
846
+
847
+ if (constants.debugLevel > 0) {
848
+ console.log('tracking data cleared');
849
+ }
850
+
851
+ this.DataSetup();
852
+ }
853
+
854
+ /**
855
+ * Logs an event with various properties to the tracker data.
856
+ * @param {Event} e - The event to log.
857
+ */
858
+ LogEvent(e) {
859
+ let eventToLog = {};
860
+
861
+ // computer stuff
862
+ eventToLog.timestamp = Object.assign(e.timeStamp);
863
+ eventToLog.time = Date().toString();
864
+ eventToLog.key = Object.assign(e.key);
865
+ eventToLog.altKey = Object.assign(e.altKey);
866
+ eventToLog.ctrlKey = Object.assign(e.ctrlKey);
867
+ eventToLog.shiftKey = Object.assign(e.shiftKey);
868
+ if (e.path) {
869
+ eventToLog.focus = Object.assign(e.path[0].tagName);
870
+ }
871
+
872
+ // settings etc, which we have to reassign otherwise they'll all be the same val
873
+ if (!this.isUndefinedOrNull(constants.position)) {
874
+ eventToLog.position = Object.assign(constants.position);
875
+ }
876
+ if (!this.isUndefinedOrNull(constants.minX)) {
877
+ eventToLog.min_x = Object.assign(constants.minX);
878
+ }
879
+ if (!this.isUndefinedOrNull(constants.maxX)) {
880
+ eventToLog.max_x = Object.assign(constants.maxX);
881
+ }
882
+ if (!this.isUndefinedOrNull(constants.minY)) {
883
+ eventToLog.min_y = Object.assign(constants.minY);
884
+ }
885
+ if (!this.isUndefinedOrNull(constants.MAX_FREQUENCY)) {
886
+ eventToLog.max_frequency = Object.assign(constants.MAX_FREQUENCY);
887
+ }
888
+ if (!this.isUndefinedOrNull(constants.MIN_FREQUENCY)) {
889
+ eventToLog.min_frequency = Object.assign(constants.MIN_FREQUENCY);
890
+ }
891
+ if (!this.isUndefinedOrNull(constants.NULL_FREQUENCY)) {
892
+ eventToLog.null_frequency = Object.assign(constants.NULL_FREQUENCY);
893
+ }
894
+ if (!this.isUndefinedOrNull(constants.MAX_SPEED)) {
895
+ eventToLog.max_speed = Object.assign(constants.MAX_SPEED);
896
+ }
897
+ if (!this.isUndefinedOrNull(constants.MIN_SPEED)) {
898
+ eventToLog.min_speed = Object.assign(constants.MIN_SPEED);
899
+ }
900
+ if (!this.isUndefinedOrNull(constants.INTERVAL)) {
901
+ eventToLog.interval = Object.assign(constants.INTERVAL);
902
+ }
903
+ if (!this.isUndefinedOrNull(constants.vol)) {
904
+ eventToLog.volume = Object.assign(constants.vol);
905
+ }
906
+ if (!this.isUndefinedOrNull(constants.autoPlayRate)) {
907
+ eventToLog.autoplay_rate = Object.assign(constants.autoPlayRate);
908
+ }
909
+ if (!this.isUndefinedOrNull(constants.colorSelected)) {
910
+ eventToLog.color = Object.assign(constants.colorSelected);
911
+ }
912
+ if (!this.isUndefinedOrNull(constants.brailleDisplayLength)) {
913
+ eventToLog.braille_display_length = Object.assign(
914
+ constants.brailleDisplayLength
915
+ );
916
+ }
917
+ if (!this.isUndefinedOrNull(constants.duration)) {
918
+ eventToLog.tone_duration = Object.assign(constants.duration);
919
+ }
920
+ if (!this.isUndefinedOrNull(constants.autoPlayOutlierRate)) {
921
+ eventToLog.autoplay_outlier_rate = Object.assign(
922
+ constants.autoPlayOutlierRate
923
+ );
924
+ }
925
+ if (!this.isUndefinedOrNull(constants.autoPlayPointsRate)) {
926
+ eventToLog.autoplay_points_rate = Object.assign(
927
+ constants.autoPlayPointsRate
928
+ );
929
+ }
930
+ if (!this.isUndefinedOrNull(constants.textMode)) {
931
+ eventToLog.text_mode = Object.assign(constants.textMode);
932
+ }
933
+ if (!this.isUndefinedOrNull(constants.sonifMode)) {
934
+ eventToLog.sonification_mode = Object.assign(constants.sonifMode);
935
+ }
936
+ if (!this.isUndefinedOrNull(constants.brailleMode)) {
937
+ eventToLog.braille_mode = Object.assign(constants.brailleMode);
938
+ }
939
+ if (!this.isUndefinedOrNull(constants.chartType)) {
940
+ eventToLog.chart_type = Object.assign(constants.chartType);
941
+ }
942
+ if (!this.isUndefinedOrNull(constants.infoDiv.innerHTML)) {
943
+ let textDisplay = Object.assign(constants.infoDiv.innerHTML);
944
+ textDisplay = textDisplay.replaceAll(/<[^>]*>?/gm, '');
945
+ eventToLog.text_display = textDisplay;
946
+ }
947
+ if (!this.isUndefinedOrNull(location.href)) {
948
+ eventToLog.location = Object.assign(location.href);
949
+ }
950
+
951
+ // chart specific values
952
+ let x_tickmark = '';
953
+ let y_tickmark = '';
954
+ let x_label = '';
955
+ let y_label = '';
956
+ let value = '';
957
+ let fill_value = '';
958
+ if (constants.chartType == 'bar') {
959
+ if (!this.isUndefinedOrNull(plot.columnLabels[position.x])) {
960
+ x_tickmark = plot.columnLabels[position.x];
961
+ }
962
+ if (!this.isUndefinedOrNull(plot.plotLegend.x)) {
963
+ x_label = plot.plotLegend.x;
964
+ }
965
+ if (!this.isUndefinedOrNull(plot.plotLegend.y)) {
966
+ y_label = plot.plotLegend.y;
967
+ }
968
+ if (!this.isUndefinedOrNull(plot.plotData[position.x])) {
969
+ value = plot.plotData[position.x];
970
+ }
971
+ } else if (constants.chartType == 'heat') {
972
+ if (!this.isUndefinedOrNull(plot.x_labels[position.x])) {
973
+ x_tickmark = plot.x_labels[position.x].trim();
974
+ }
975
+ if (!this.isUndefinedOrNull(plot.y_labels[position.y])) {
976
+ y_tickmark = plot.y_labels[position.y].trim();
977
+ }
978
+ if (!this.isUndefinedOrNull(plot.x_group_label)) {
979
+ x_label = plot.x_group_label;
980
+ }
981
+ if (!this.isUndefinedOrNull(plot.y_group_label)) {
982
+ y_label = plot.y_group_label;
983
+ }
984
+ if (!this.isUndefinedOrNull(plot.values)) {
985
+ if (!this.isUndefinedOrNull(plot.values[position.x][position.y])) {
986
+ value = plot.values[position.x][position.y];
987
+ }
988
+ }
989
+ if (!this.isUndefinedOrNull(plot.group_labels[2])) {
990
+ fill_value = plot.group_labels[2];
991
+ }
992
+ } else if (constants.chartType == 'box') {
993
+ let plotPos =
994
+ constants.plotOrientation == 'vert' ? position.x : position.y;
995
+ let sectionPos =
996
+ constants.plotOrientation == 'vert' ? position.y : position.x;
997
+
998
+ if (!this.isUndefinedOrNull(plot.x_group_label)) {
999
+ x_label = plot.x_group_label;
1000
+ }
1001
+ if (!this.isUndefinedOrNull(plot.y_group_label)) {
1002
+ y_label = plot.y_group_label;
1003
+ }
1004
+ if (constants.plotOrientation == 'vert') {
1005
+ if (plotPos > -1 && sectionPos > -1) {
1006
+ if (
1007
+ !this.isUndefinedOrNull(plot.plotData[plotPos][sectionPos].label)
1008
+ ) {
1009
+ y_tickmark = plot.plotData[plotPos][sectionPos].label;
1010
+ }
1011
+ if (!this.isUndefinedOrNull(plot.x_labels[position.x])) {
1012
+ x_tickmark = plot.x_labels[position.x];
1013
+ }
1014
+ if (
1015
+ !this.isUndefinedOrNull(plot.plotData[plotPos][sectionPos].values)
1016
+ ) {
1017
+ value = plot.plotData[plotPos][sectionPos].values;
1018
+ } else if (
1019
+ !this.isUndefinedOrNull(plot.plotData[plotPos][sectionPos].y)
1020
+ ) {
1021
+ value = plot.plotData[plotPos][sectionPos].y;
1022
+ }
1023
+ }
1024
+ } else {
1025
+ if (plotPos > -1 && sectionPos > -1) {
1026
+ if (
1027
+ !this.isUndefinedOrNull(plot.plotData[plotPos][sectionPos].label)
1028
+ ) {
1029
+ x_tickmark = plot.plotData[plotPos][sectionPos].label;
1030
+ }
1031
+ if (!this.isUndefinedOrNull(plot.y_labels[position.y])) {
1032
+ y_tickmark = plot.y_labels[position.y];
1033
+ }
1034
+ if (
1035
+ !this.isUndefinedOrNull(plot.plotData[plotPos][sectionPos].values)
1036
+ ) {
1037
+ value = plot.plotData[plotPos][sectionPos].values;
1038
+ } else if (
1039
+ !this.isUndefinedOrNull(plot.plotData[plotPos][sectionPos].x)
1040
+ ) {
1041
+ value = plot.plotData[plotPos][sectionPos].x;
1042
+ }
1043
+ }
1044
+ }
1045
+ } else if (constants.chartType == 'point') {
1046
+ if (!this.isUndefinedOrNull(plot.x_group_label)) {
1047
+ x_label = plot.x_group_label;
1048
+ }
1049
+ if (!this.isUndefinedOrNull(plot.y_group_label)) {
1050
+ y_label = plot.y_group_label;
1051
+ }
1052
+
1053
+ if (!this.isUndefinedOrNull(plot.x[position.x])) {
1054
+ x_tickmark = plot.x[position.x];
1055
+ }
1056
+ if (!this.isUndefinedOrNull(plot.y[position.x])) {
1057
+ y_tickmark = plot.y[position.x];
1058
+ }
1059
+
1060
+ value = [x_tickmark, y_tickmark];
1061
+ }
1062
+
1063
+ eventToLog.x_tickmark = Object.assign(x_tickmark);
1064
+ eventToLog.y_tickmark = Object.assign(y_tickmark);
1065
+ eventToLog.x_label = Object.assign(x_label);
1066
+ eventToLog.y_label = Object.assign(y_label);
1067
+ eventToLog.value = Object.assign(value);
1068
+ eventToLog.fill_value = Object.assign(fill_value);
1069
+
1070
+ //console.log("x_tickmark: '", x_tickmark, "', y_tickmark: '", y_tickmark, "', x_label: '", x_label, "', y_label: '", y_label, "', value: '", value, "', fill_value: '", fill_value);
1071
+
1072
+ let data = this.GetTrackerData();
1073
+ data.events.push(eventToLog);
1074
+ this.SaveTrackerData(data);
1075
+ }
1076
+
1077
+ /**
1078
+ * Checks if the given item is undefined or null.
1079
+ * @param {*} item - The item to check.
1080
+ * @returns {boolean} - Returns true if the item is undefined or null, else false.
1081
+ */
1082
+ isUndefinedOrNull(item) {
1083
+ try {
1084
+ return item === undefined || item === null;
1085
+ } catch {
1086
+ return true;
1087
+ }
1088
+ }
1089
+ }
1090
+
1091
+ /**
1092
+ * Represents a Review object.
1093
+ * @class
1094
+ */
1095
+ class Review {
1096
+ constructor() {}
1097
+
1098
+ /**
1099
+ * Toggles the review mode on or off.
1100
+ * @param {boolean} [onoff=true] - Whether to turn review mode on or off. Default is true.
1101
+ */
1102
+ ToggleReviewMode(onoff = true) {
1103
+ // true means on or show
1104
+ if (onoff) {
1105
+ constants.reviewSaveSpot = document.activeElement;
1106
+ constants.review_container.classList.remove('hidden');
1107
+ constants.reviewSaveBrailleMode = constants.brailleMode;
1108
+ constants.review.focus();
1109
+
1110
+ display.announceText('Review on');
1111
+ } else {
1112
+ constants.review_container.classList.add('hidden');
1113
+ if (constants.reviewSaveBrailleMode == 'on') {
1114
+ // we have to turn braille mode back on
1115
+ display.toggleBrailleMode('on');
1116
+ } else {
1117
+ constants.reviewSaveSpot.focus();
1118
+ }
1119
+ display.announceText('Review off');
1120
+ }
1121
+ }
1122
+ }
1123
+
1124
+ /**
1125
+ * Represents a class for logging errors.
1126
+ */
1127
+ class LogError {
1128
+ constructor() {}
1129
+
1130
+ /**
1131
+ * Logs the absent element and turns off visual highlighting.
1132
+ * @param {string} a - The absent element to log.
1133
+ */
1134
+ LogAbsentElement(a) {
1135
+ console.log(a, 'not found. Visual highlighting is turned off.');
1136
+ }
1137
+
1138
+ /**
1139
+ * Logs a critical element and indicates that MAIDR is unable to run.
1140
+ * @param {string} a - The critical element to log.
1141
+ */
1142
+ LogCriticalElement(a) {
1143
+ consolelog(a, 'is critical. MAIDR unable to run');
1144
+ }
1145
+
1146
+ /**
1147
+ * Logs a message indicating that two values do not have the same length.
1148
+ * @param {*} a - The first value to compare.
1149
+ * @param {*} b - The second value to compare.
1150
+ */
1151
+ LogDifferentLengths(a, b) {
1152
+ console.log(
1153
+ a,
1154
+ 'and',
1155
+ b,
1156
+ 'do not have the same length. Visual highlighting is turned off.'
1157
+ );
1158
+ }
1159
+
1160
+ /**
1161
+ * Logs a message indicating that too many elements were found and only the first n elements will be highlighted.
1162
+ * @param {string} a - The type of element being highlighted.
1163
+ * @param {number} b - The maximum number of elements to highlight.
1164
+ */
1165
+ LogTooManyElements(a, b) {
1166
+ console.log(
1167
+ 'Too many',
1168
+ a,
1169
+ 'elements. Only the first',
1170
+ b,
1171
+ 'will be highlighted.'
1172
+ );
1173
+ }
1174
+
1175
+ /**
1176
+ * Logs a message indicating that the provided parameter is not an array.
1177
+ * @param {*} a - The parameter that is not an array.
1178
+ */
1179
+ LogNotArray(a) {
1180
+ console.log(a, 'is not an array. Visual highlighting is turned off.');
1181
+ }
1182
+ }