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,1184 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>display.js - Documentation</title>
7
+
8
+ <script src="scripts/prettify/prettify.js"></script>
9
+ <script src="scripts/prettify/lang-css.js"></script>
10
+ <!--[if lt IE 9]>
11
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
12
+ <![endif]-->
13
+ <link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
16
+ </head>
17
+ <body>
18
+
19
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
20
+ <label for="nav-trigger" class="navicon-button x">
21
+ <div class="navicon"></div>
22
+ </label>
23
+
24
+ <label for="nav-trigger" class="overlay"></label>
25
+
26
+ <nav>
27
+ <li class="nav-link nav-home-link"><a href="index.html">Home</a></li><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Audio.html">Audio</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Audio.html#KillSmooth">KillSmooth</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Audio.html#PlayNull">PlayNull</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Audio.html#SlideBetween">SlideBetween</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Audio.html#compressorSetup">compressorSetup</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Audio.html#playOscillator">playOscillator</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Audio.html#playSmooth">playSmooth</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Audio.html#playTone">playTone</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Constants.html">Constants</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Description.html">Description</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Description.html#CreateComponent">CreateComponent</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Description.html#Destroy">Destroy</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Description.html#PopulateData">PopulateData</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Description.html#Toggle">Toggle</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Helper.html">Helper</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Helper.html#.containsObject">containsObject</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="LogError.html">LogError</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="LogError.html#LogAbsentElement">LogAbsentElement</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="LogError.html#LogCriticalElement">LogCriticalElement</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="LogError.html#LogDifferentLengths">LogDifferentLengths</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="LogError.html#LogNotArray">LogNotArray</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="LogError.html#LogTooManyElements">LogTooManyElements</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Menu.html">Menu</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Menu.html#CreateMenu">CreateMenu</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Menu.html#LoadDataFromLocalStorage">LoadDataFromLocalStorage</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Menu.html#PopulateData">PopulateData</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Menu.html#SaveData">SaveData</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Menu.html#Toggle">Toggle</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Position.html">Position</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Resources.html">Resources</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Resources.html#GetString">GetString</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Review.html">Review</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Review.html#ToggleReviewMode">ToggleReviewMode</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Tracker.html">Tracker</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Tracker.html#DataSetup">DataSetup</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Tracker.html#Delete">Delete</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Tracker.html#DownloadTrackerData">DownloadTrackerData</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Tracker.html#GetTrackerData">GetTrackerData</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Tracker.html#LogEvent">LogEvent</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Tracker.html#SaveTrackerData">SaveTrackerData</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Tracker.html#isUndefinedOrNull">isUndefinedOrNull</a></span></li>
28
+ </nav>
29
+
30
+ <div id="main">
31
+
32
+ <h1 class="page-title">display.js</h1>
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+ <section>
41
+ <article>
42
+ <pre class="prettyprint source linenums"><code>class Display {
43
+ constructor() {
44
+ this.infoDiv = constants.infoDiv;
45
+
46
+ this.x = {};
47
+ this.x.id = 'x';
48
+ this.x.textBase = 'x-value: ';
49
+
50
+ this.y = {};
51
+ this.y.id = 'y';
52
+ this.y.textBase = 'y-value: ';
53
+
54
+ this.boxplotGridPlaceholders = [
55
+ resources.GetString('lower_outlier'),
56
+ resources.GetString('min'),
57
+ resources.GetString('25'),
58
+ resources.GetString('50'),
59
+ resources.GetString('75'),
60
+ resources.GetString('max'),
61
+ resources.GetString('upper_outlier'),
62
+ ];
63
+ }
64
+
65
+ toggleTextMode() {
66
+ if (constants.textMode == 'off') {
67
+ constants.textMode = 'terse';
68
+ } else if (constants.textMode == 'terse') {
69
+ constants.textMode = 'verbose';
70
+ } else if (constants.textMode == 'verbose') {
71
+ constants.textMode = 'off';
72
+ }
73
+
74
+ this.announceText(
75
+ '&lt;span aria-hidden="true">Text mode:&lt;/span> ' + constants.textMode
76
+ );
77
+ }
78
+
79
+ toggleBrailleMode(onoff) {
80
+ if (constants.chartType == 'point') {
81
+ this.announceText('Braille is not supported in point layer.');
82
+ return;
83
+ }
84
+ if (typeof onoff === 'undefined') {
85
+ if (typeof constants.brailleMode === 'undefined') {
86
+ constants.brailleMode = 'off';
87
+ onoff = constants.brailleMode == 'on';
88
+ } else {
89
+ // switch on/off
90
+ if (constants.brailleMode == 'on') {
91
+ onoff = 'off';
92
+ } else {
93
+ onoff = 'on';
94
+ }
95
+ constants.brailleMode = onoff;
96
+ }
97
+ }
98
+ if (onoff == 'on') {
99
+ if (constants.chartType == 'box') {
100
+ // braille mode is on before any plot is selected
101
+ if (
102
+ constants.plotOrientation != 'vert' &amp;&amp;
103
+ position.x == -1 &amp;&amp;
104
+ position.y == plot.plotData.length
105
+ ) {
106
+ position.x += 1;
107
+ position.y -= 1;
108
+ } else if (
109
+ constants.plotOrientation == 'vert' &amp;&amp;
110
+ position.x == 0 &amp;&amp;
111
+ position.y == plot.plotData[0].length - 1
112
+ ) {
113
+ // do nothing; don't think there's any problem
114
+ }
115
+ }
116
+
117
+ constants.brailleMode = 'on';
118
+ document
119
+ .getElementById(constants.braille_container_id)
120
+ .classList.remove('hidden');
121
+ constants.brailleInput.focus();
122
+ constants.brailleInput.setSelectionRange(position.x, position.x);
123
+
124
+ this.SetBraille();
125
+
126
+ if (constants.chartType == 'heat') {
127
+ let pos = position.y * (plot.num_cols + 1) + position.x;
128
+ constants.brailleInput.setSelectionRange(pos, pos);
129
+ }
130
+
131
+ // braille mode is on before navigation of chart
132
+ // very important to make sure braille works properly
133
+ if (position.x == -1 &amp;&amp; position.y == -1) {
134
+ constants.brailleInput.setSelectionRange(0, 0);
135
+ }
136
+ } else {
137
+ constants.brailleMode = 'off';
138
+ document
139
+ .getElementById(constants.braille_container_id)
140
+ .classList.add('hidden');
141
+
142
+ if (constants.review_container) {
143
+ if (!constants.review_container.classList.contains('hidden')) {
144
+ constants.review.focus();
145
+ } else {
146
+ constants.chart.focus();
147
+ }
148
+ } else {
149
+ constants.chart.focus();
150
+ }
151
+ }
152
+
153
+ this.announceText('Braille ' + constants.brailleMode);
154
+ }
155
+
156
+ toggleSonificationMode() {
157
+ if (
158
+ constants.chartType == 'point' ||
159
+ constants.chartType == 'stacked_bar' ||
160
+ constants.chartType == 'stacked_normalized_bar' ||
161
+ constants.chartType == 'dodged_bar'
162
+ ) {
163
+ if (constants.sonifMode == 'off') {
164
+ constants.sonifMode = 'on';
165
+ this.announceText(resources.GetString('son_sep'));
166
+ } else if (constants.sonifMode == 'on') {
167
+ constants.sonifMode = 'same';
168
+ this.announceText(resources.GetString('son_same'));
169
+ } else if (constants.sonifMode == 'same') {
170
+ constants.sonifMode = 'off';
171
+ this.announceText(resources.GetString('son_off'));
172
+ }
173
+ } else {
174
+ if (constants.sonifMode == 'off') {
175
+ constants.sonifMode = 'on';
176
+ this.announceText(resources.GetString('son_on'));
177
+ } else {
178
+ constants.sonifMode = 'off';
179
+ this.announceText(resources.GetString('son_off'));
180
+ }
181
+ }
182
+ }
183
+
184
+ changeChartLayer(updown = 'down') {
185
+ // get possible chart types, where we are, and move between them
186
+ let chartTypes = maidr.type;
187
+ if (Array.isArray(chartTypes)) {
188
+ let currentIndex = chartTypes.indexOf(constants.chartType);
189
+ if (updown == 'down') {
190
+ if (currentIndex == 0) {
191
+ //constants.chartType = chartTypes[chartTypes.length - 1];
192
+ } else {
193
+ constants.chartType = chartTypes[currentIndex - 1];
194
+ this.announceText('Switched to ' + constants.chartType); // todo: connect this to a resource file so it can be localized
195
+ }
196
+ } else {
197
+ if (currentIndex == chartTypes.length - 1) {
198
+ //constants.chartType = chartTypes[0];
199
+ } else {
200
+ constants.chartType = chartTypes[currentIndex + 1];
201
+ this.announceText('Switched to ' + constants.chartType); // todo: connect this to a resource file so it can be localized
202
+ }
203
+ }
204
+ }
205
+
206
+ // update position relative to where we were on the previous layer
207
+ // newX = oldX * newLen / oldLen
208
+ if (constants.chartType == 'point') {
209
+ position.x = Math.round(
210
+ ((plot.x.length - 1) * positionL1.x) / (plot.curvePoints.length - 1)
211
+ );
212
+ } else if (constants.chartType == 'smooth') {
213
+ // reverse math of the above
214
+ positionL1.x = Math.round(
215
+ ((plot.curvePoints.length - 1) * position.x) / (plot.x.length - 1)
216
+ );
217
+ }
218
+ }
219
+
220
+ announceText(txt) {
221
+ constants.announceContainer.innerHTML = txt;
222
+ }
223
+
224
+ UpdateBraillePos() {
225
+ if (
226
+ constants.chartType == 'bar' ||
227
+ constants.chartType == 'hist' ||
228
+ constants.chartType == 'line'
229
+ ) {
230
+ constants.brailleInput.setSelectionRange(position.x, position.x);
231
+ } else if (
232
+ constants.chartType == 'stacked_bar' ||
233
+ constants.chartType == 'stacked_normalized_bar' ||
234
+ constants.chartType == 'dodged_bar'
235
+ ) {
236
+ // if we're not on the top y position
237
+ let pos = null;
238
+ if (position.y &lt; plot.plotData[0].length - 1) {
239
+ pos = position.x;
240
+ } else {
241
+ pos = position.x * (plot.fill.length + 1) + position.y;
242
+ }
243
+ constants.brailleInput.setSelectionRange(pos, pos);
244
+ } else if (constants.chartType == 'heat') {
245
+ let pos = position.y * (plot.num_cols + 1) + position.x;
246
+ constants.brailleInput.setSelectionRange(pos, pos);
247
+ } else if (constants.chartType == 'box') {
248
+ // on box we extend characters a lot and have blanks, so we go to our type
249
+ let sectionPos =
250
+ constants.plotOrientation == 'vert' ? position.y : position.x;
251
+ let targetLabel = this.boxplotGridPlaceholders[sectionPos];
252
+ let haveTargetLabel = false;
253
+ let adjustedPos = 0;
254
+ if (constants.brailleData) {
255
+ for (let i = 0; i &lt; constants.brailleData.length; i++) {
256
+ if (constants.brailleData[i].type != 'blank') {
257
+ if (
258
+ resources.GetString(constants.brailleData[i].type) == targetLabel
259
+ ) {
260
+ haveTargetLabel = true;
261
+ break;
262
+ }
263
+ }
264
+ adjustedPos += constants.brailleData[i].numChars;
265
+ }
266
+ } else {
267
+ throw 'Braille data not set up, cannot move cursor in braille, sorry.';
268
+ }
269
+ // but sometimes we don't have our targetLabel, go to the start
270
+ // future todo: look for nearby label and go to the nearby side of that
271
+ if (!haveTargetLabel) {
272
+ adjustedPos = 0;
273
+ }
274
+
275
+ constants.brailleInput.setSelectionRange(adjustedPos, adjustedPos);
276
+ } else if (
277
+ singleMaidr.type == 'point' ||
278
+ singleMaidr.type.includes('point')
279
+ ) {
280
+ constants.brailleInput.setSelectionRange(positionL1.x, positionL1.x);
281
+ }
282
+ }
283
+
284
+ displayValues() {
285
+ // we build an html text string to output to both visual users and aria live based on what chart we're on, our position, and the mode
286
+ // note: we do this all as one string rather than changing individual element IDs so that aria-live receives a single update
287
+
288
+ let output = '';
289
+ let verboseText = '';
290
+ let reviewText = '';
291
+ if (constants.chartType == 'bar') {
292
+ // {legend x} is {colname x}, {legend y} is {value y}
293
+ if (plot.plotLegend.x.length > 0 &amp;&amp; plot.columnLabels[position.x]) {
294
+ verboseText =
295
+ plot.plotLegend.x + ' is ' + plot.columnLabels[position.x] + ', ';
296
+ }
297
+ if (plot.plotData[position.x]) {
298
+ verboseText += plot.plotLegend.y + ' is ' + plot.plotData[position.x];
299
+ }
300
+ if (constants.textMode == 'off') {
301
+ // do nothing :D
302
+ } else if (constants.textMode == 'terse') {
303
+ // {colname} {value}
304
+ output +=
305
+ '&lt;p>' +
306
+ plot.columnLabels[position.x] +
307
+ ' ' +
308
+ plot.plotData[position.x] +
309
+ '&lt;/p>\n';
310
+ } else if (constants.textMode == 'verbose') {
311
+ output += '&lt;p>' + verboseText + '&lt;/p>\n';
312
+ }
313
+ } else if (constants.chartType == 'heat') {
314
+ // col name and value
315
+ if (constants.navigation == 1) {
316
+ verboseText +=
317
+ plot.x_group_label +
318
+ ' ' +
319
+ plot.x_labels[position.x] +
320
+ ', ' +
321
+ plot.y_group_label +
322
+ ' ' +
323
+ plot.y_labels[position.y] +
324
+ ', ' +
325
+ plot.fill +
326
+ ' is ';
327
+ // if (constants.hasRect) {
328
+ verboseText += plot.plotData[2][position.y][position.x];
329
+ // }
330
+ } else {
331
+ verboseText +=
332
+ plot.y_group_label +
333
+ ' ' +
334
+ plot.y_labels[position.y] +
335
+ ', ' +
336
+ plot.x_group_label +
337
+ ' ' +
338
+ plot.x_labels[position.x] +
339
+ ', ' +
340
+ plot.fill +
341
+ ' is ';
342
+ // if (constants.hasRect) {
343
+ verboseText += plot.plotData[2][position.y][position.x];
344
+ // }
345
+ }
346
+ // terse and verbose alternate between columns and rows
347
+ if (constants.textMode == 'off') {
348
+ // do nothing :D
349
+ } else if (constants.textMode == 'terse') {
350
+ // value only
351
+ if (constants.navigation == 1) {
352
+ // column navigation
353
+ output +=
354
+ '&lt;p>' +
355
+ plot.x_labels[position.x] +
356
+ ', ' +
357
+ plot.plotData[2][position.y][position.x] +
358
+ '&lt;/p>\n';
359
+ } else {
360
+ // row navigation
361
+ output +=
362
+ '&lt;p>' +
363
+ plot.y_labels[position.y] +
364
+ ', ' +
365
+ plot.plotData[2][position.y][position.x] +
366
+ '&lt;/p>\n';
367
+ }
368
+ } else if (constants.textMode == 'verbose') {
369
+ output += '&lt;p>' + verboseText + '&lt;/p>\n';
370
+ }
371
+ } else if (constants.chartType == 'box') {
372
+ // setup
373
+ let val = 0;
374
+ let numPoints = 1;
375
+ let isOutlier = false;
376
+ let plotPos =
377
+ constants.plotOrientation == 'vert' ? position.x : position.y;
378
+ let sectionKey = plot.GetSectionKey(
379
+ constants.plotOrientation == 'vert' ? position.y : position.x
380
+ );
381
+ let textTerse = '';
382
+ let textVerbose = '';
383
+
384
+ if (sectionKey == 'lower_outlier' || sectionKey == 'upper_outlier') {
385
+ isOutlier = true;
386
+ }
387
+ if (plot.plotData[plotPos][sectionKey] == null) {
388
+ val = '';
389
+ if (isOutlier) numPoints = 0;
390
+ } else if (isOutlier) {
391
+ val = plot.plotData[plotPos][sectionKey].join(', ');
392
+ numPoints = plot.plotData[plotPos][sectionKey].length;
393
+ } else {
394
+ val = plot.plotData[plotPos][sectionKey];
395
+ }
396
+
397
+ // set output
398
+
399
+ // group label for verbose
400
+ if (constants.navigation) {
401
+ if (plot.x_group_label) textVerbose += plot.x_group_label;
402
+ } else if (!constants.navigation) {
403
+ if (plot.y_group_label) textVerbose += plot.y_group_label;
404
+ }
405
+ // and axes label
406
+ if (constants.navigation) {
407
+ if (plot.x_labels[plotPos]) {
408
+ textVerbose += ' is ';
409
+ textTerse += plot.x_labels[plotPos] + ', ';
410
+ textVerbose += plot.x_labels[plotPos] + ', ';
411
+ } else {
412
+ textVerbose += ', ';
413
+ }
414
+ } else if (!constants.navigation) {
415
+ if (plot.y_labels[plotPos]) {
416
+ textVerbose += ' is ';
417
+ textTerse += plot.y_labels[plotPos] + ', ';
418
+ textVerbose += plot.y_labels[plotPos] + ', ';
419
+ } else {
420
+ textVerbose += ', ';
421
+ }
422
+ }
423
+ // outliers
424
+ if (isOutlier) {
425
+ textTerse += numPoints + ' ';
426
+ textVerbose += numPoints + ' ';
427
+ }
428
+ // label
429
+ textVerbose += resources.GetString(sectionKey);
430
+ if (numPoints == 1) textVerbose += ' is ';
431
+ else {
432
+ textVerbose += 's ';
433
+ if (numPoints > 1) textVerbose += ' are ';
434
+ }
435
+ if (
436
+ isOutlier ||
437
+ (constants.navigation &amp;&amp; constants.plotOrientation == 'horz') ||
438
+ (!constants.navigation &amp;&amp; constants.plotOrientation == 'vert')
439
+ ) {
440
+ textTerse += resources.GetString(sectionKey);
441
+
442
+ // grammar
443
+ if (numPoints != 1) {
444
+ textTerse += 's';
445
+ }
446
+ textTerse += ' ';
447
+ }
448
+ // val
449
+ if (plot.plotData[plotPos][sectionKey] != null &amp;&amp; !isOutlier) {
450
+ textTerse += 'empty';
451
+ textVerbose += 'empty';
452
+ } else {
453
+ textTerse += val;
454
+ textVerbose += val;
455
+ }
456
+
457
+ verboseText = textVerbose; // yeah it's an extra var, who cares
458
+ if (constants.textMode == 'verbose')
459
+ output = '&lt;p>' + textVerbose + '&lt;/p>\n';
460
+ else if (constants.textMode == 'terse')
461
+ output = '&lt;p>' + textTerse + '&lt;/p>\n';
462
+ } else if (
463
+ singleMaidr.type == 'point' ||
464
+ singleMaidr.type.includes('point')
465
+ ) {
466
+ if (constants.chartType == 'point') {
467
+ // point layer
468
+ verboseText +=
469
+ plot.x_group_label +
470
+ ' ' +
471
+ plot.x[position.x] +
472
+ ', ' +
473
+ plot.y_group_label +
474
+ ' [' +
475
+ plot.y[position.x].join(', ') +
476
+ ']';
477
+
478
+ if (constants.textMode == 'off') {
479
+ // do nothing
480
+ } else if (constants.textMode == 'terse') {
481
+ output +=
482
+ '&lt;p>' +
483
+ plot.x[position.x] +
484
+ ', ' +
485
+ '[' +
486
+ plot.y[position.x].join(', ') +
487
+ ']' +
488
+ '&lt;/p>\n';
489
+ } else if (constants.textMode == 'verbose') {
490
+ // set from verboseText
491
+ }
492
+ } else if (constants.chartType == 'smooth') {
493
+ // best fit smooth layer
494
+ verboseText +=
495
+ plot.x_group_label +
496
+ ' ' +
497
+ plot.curveX[positionL1.x] +
498
+ ', ' +
499
+ plot.y_group_label +
500
+ ' ' +
501
+ plot.curvePoints[positionL1.x]; // verbose mode: x and y values
502
+
503
+ if (constants.textMode == 'off') {
504
+ // do nothing
505
+ } else if (constants.textMode == 'terse') {
506
+ // terse mode: gradient trend
507
+ // output += '&lt;p>' + plot.gradient[positionL1.x] + '&lt;p>\n';
508
+
509
+ // display absolute gradient of the graph
510
+ output += '&lt;p>' + plot.curvePoints[positionL1.x] + '&lt;p>\n';
511
+ } else if (constants.textMode == 'verbose') {
512
+ // set from verboseText
513
+ }
514
+ }
515
+ if (constants.textMode == 'verbose')
516
+ output = '&lt;p>' + verboseText + '&lt;/p>\n';
517
+ } else if (constants.chartType == 'hist') {
518
+ if (constants.textMode == 'terse') {
519
+ // terse: {x}, {y}
520
+ output =
521
+ '&lt;p>' +
522
+ plot.plotData[position.x].x +
523
+ ', ' +
524
+ plot.plotData[position.x].y +
525
+ '&lt;/p>\n';
526
+ } else if (constants.textMode == 'verbose') {
527
+ // verbose: {xlabel} is xmin through xmax, {ylabel} is y
528
+ output = '&lt;p>';
529
+ if (plot.legendX) {
530
+ output = plot.legendX + ' is ';
531
+ }
532
+ output += plot.plotData[position.x].xmin;
533
+ output += ' through ' + plot.plotData[position.x].xmax + ', ';
534
+ if (plot.legendY) {
535
+ output += plot.legendY + ' is ';
536
+ }
537
+ output += plot.plotData[position.x].y;
538
+ }
539
+ } else if (constants.chartType == 'line') {
540
+ // line layer
541
+ verboseText +=
542
+ plot.x_group_label +
543
+ ' is ' +
544
+ plot.pointValuesX[position.x] +
545
+ ', ' +
546
+ plot.y_group_label +
547
+ ' is ' +
548
+ plot.pointValuesY[position.x];
549
+
550
+ if (constants.textMode == 'off') {
551
+ // do nothing
552
+ } else if (constants.textMode == 'terse') {
553
+ output +=
554
+ '&lt;p>' +
555
+ plot.pointValuesX[position.x] +
556
+ ', ' +
557
+ plot.pointValuesY[position.x] +
558
+ '&lt;/p>\n';
559
+ } else if (constants.textMode == 'verbose') {
560
+ // set from verboseText
561
+ output += '&lt;p>' + verboseText + '&lt;/p>\n';
562
+ }
563
+ } else if (
564
+ constants.chartType == 'stacked_bar' ||
565
+ constants.chartType == 'stacked_normalized_bar' ||
566
+ constants.chartType == 'dodged_bar'
567
+ ) {
568
+ // {legend x} is {colname x}, {legend y} is {colname y}, value is {plotData[x][y]}
569
+ verboseText += plot.plotLegend.x + ' is ' + plot.level[position.x] + ', ';
570
+ verboseText += plot.plotLegend.y + ' is ' + plot.fill[position.y] + ', ';
571
+ verboseText += 'value is ' + plot.plotData[position.x][position.y];
572
+
573
+ if (constants.textMode == 'off') {
574
+ // do nothing
575
+ } else if (constants.textMode == 'terse') {
576
+ // navigation == 1 ? {colname x} : {colname y} is {plotData[x][y]}
577
+ if (constants.navigation == 1) {
578
+ output +=
579
+ '&lt;p>' +
580
+ plot.level[position.x] +
581
+ ' is ' +
582
+ plot.plotData[position.x][position.y] +
583
+ '&lt;/p>\n';
584
+ } else {
585
+ output +=
586
+ '&lt;p>' +
587
+ plot.fill[position.y] +
588
+ ' is ' +
589
+ plot.plotData[position.x][position.y] +
590
+ '&lt;/p>\n';
591
+ }
592
+ } else {
593
+ output += '&lt;p>' + verboseText + '&lt;/p>\n';
594
+ }
595
+ }
596
+
597
+ if (constants.infoDiv) constants.infoDiv.innerHTML = output;
598
+ if (constants.review) {
599
+ if (output.length > 0) {
600
+ constants.review.value = output.replace(/&lt;[^>]*>?/gm, '');
601
+ } else {
602
+ constants.review.value = verboseText;
603
+ }
604
+ }
605
+ }
606
+
607
+ displayInfo(textType, textValue) {
608
+ if (textType) {
609
+ if (textValue) {
610
+ if (constants.textMode == 'terse') {
611
+ constants.infoDiv.innerHTML = '&lt;p>' + textValue + '&lt;p>';
612
+ } else if (constants.textMode == 'verbose') {
613
+ let capsTextType =
614
+ textType.charAt(0).toUpperCase() + textType.slice(1);
615
+ constants.infoDiv.innerHTML =
616
+ '&lt;p>' + capsTextType + ' is ' + textValue + '&lt;p>';
617
+ }
618
+ } else {
619
+ let aOrAn = ['a', 'e', 'i', 'o', 'u'].includes(textType.charAt(0))
620
+ ? 'an'
621
+ : 'a';
622
+
623
+ constants.infoDiv.innerHTML =
624
+ '&lt;p>Plot does not have ' + aOrAn + ' ' + textType + '&lt;p>';
625
+ }
626
+ }
627
+ }
628
+
629
+ SetBraille() {
630
+ let brailleArray = [];
631
+
632
+ if (constants.chartType == 'heat') {
633
+ let range = (constants.maxY - constants.minY) / 3;
634
+ let low = constants.minY + range;
635
+ let medium = low + range;
636
+ let high = medium + range;
637
+ for (let i = 0; i &lt; plot.y_coord.length; i++) {
638
+ for (let j = 0; j &lt; plot.x_coord.length; j++) {
639
+ if (plot.values[i][j] == 0) {
640
+ brailleArray.push('⠀');
641
+ } else if (plot.values[i][j] &lt;= low) {
642
+ brailleArray.push('⠤');
643
+ } else if (plot.values[i][j] &lt;= medium) {
644
+ brailleArray.push('⠒');
645
+ } else {
646
+ brailleArray.push('⠉');
647
+ }
648
+ }
649
+ brailleArray.push('⠳');
650
+ }
651
+ } else if (
652
+ constants.chartType == 'stacked_bar' ||
653
+ constants.chartType == 'stacked_normalized_bar' ||
654
+ constants.chartType == 'dodged_bar'
655
+ ) {
656
+ // if we're not on the top y position, display just this level, using local min max
657
+ if (position.y &lt; plot.plotData[0].length - 1) {
658
+ let localMin = null;
659
+ let localMax = null;
660
+ for (let i = 0; i &lt; plot.plotData.length; i++) {
661
+ if (i == 0) {
662
+ localMin = plot.plotData[i][position.y];
663
+ localMax = plot.plotData[i][position.y];
664
+ } else {
665
+ if (plot.plotData[i][position.y] &lt; localMin) {
666
+ localMin = plot.plotData[i][position.y];
667
+ }
668
+ if (plot.plotData[i][position.y] > localMax) {
669
+ localMax = plot.plotData[i][position.y];
670
+ }
671
+ }
672
+ }
673
+ let range = (localMax - localMin) / 4;
674
+ let low = localMin + range;
675
+ let medium = low + range;
676
+ let medium_high = medium + range;
677
+ for (let i = 0; i &lt; plot.plotData.length; i++) {
678
+ if (plot.plotData[i][position.y] == 0) {
679
+ brailleArray.push('⠀');
680
+ } else if (plot.plotData[i][position.y] &lt;= low) {
681
+ brailleArray.push('⣀');
682
+ } else if (plot.plotData[i][position.y] &lt;= medium) {
683
+ brailleArray.push('⠤');
684
+ } else if (plot.plotData[i][position.y] &lt;= medium_high) {
685
+ brailleArray.push('⠒');
686
+ } else {
687
+ brailleArray.push('⠉');
688
+ }
689
+ }
690
+ } else {
691
+ // all mode, do braille similar to heatmap, with all data and seperator
692
+ for (let i = 0; i &lt; plot.plotData.length; i++) {
693
+ let range = (constants.maxY - constants.minY) / 4;
694
+ let low = constants.minY + range;
695
+ let medium = low + range;
696
+ let medium_high = medium + range;
697
+ for (let j = 0; j &lt; plot.plotData[i].length; j++) {
698
+ if (plot.plotData[i][j] == 0) {
699
+ brailleArray.push('⠀');
700
+ } else if (plot.plotData[i][j] &lt;= low) {
701
+ brailleArray.push('⣀');
702
+ } else if (plot.plotData[i][j] &lt;= medium) {
703
+ brailleArray.push('⠤');
704
+ } else if (plot.plotData[i][j] &lt;= medium_high) {
705
+ brailleArray.push('⠒');
706
+ } else {
707
+ brailleArray.push('⠉');
708
+ }
709
+ }
710
+ brailleArray.push('⠳');
711
+ }
712
+ }
713
+ } else if (constants.chartType == 'bar') {
714
+ let range = (constants.maxY - constants.minY) / 4;
715
+ let low = constants.minY + range;
716
+ let medium = low + range;
717
+ let medium_high = medium + range;
718
+ for (let i = 0; i &lt; plot.plotData.length; i++) {
719
+ if (plot.plotData[i] &lt;= low) {
720
+ brailleArray.push('⣀');
721
+ } else if (plot.plotData[i] &lt;= medium) {
722
+ brailleArray.push('⠤');
723
+ } else if (plot.plotData[i] &lt;= medium_high) {
724
+ brailleArray.push('⠒');
725
+ } else {
726
+ brailleArray.push('⠉');
727
+ }
728
+ }
729
+ } else if (constants.chartType == 'smooth') {
730
+ let range = (plot.curveMaxY - plot.curveMinY) / 4;
731
+ let low = plot.curveMinY + range;
732
+ let medium = low + range;
733
+ let medium_high = medium + range;
734
+ let high = medium_high + range;
735
+ for (let i = 0; i &lt; plot.curvePoints.length; i++) {
736
+ if (plot.curvePoints[i] &lt;= low) {
737
+ brailleArray.push('⣀');
738
+ } else if (plot.curvePoints[i] &lt;= medium) {
739
+ brailleArray.push('⠤');
740
+ } else if (plot.curvePoints[i] &lt;= medium_high) {
741
+ brailleArray.push('⠒');
742
+ } else if (plot.curvePoints[i] &lt;= high) {
743
+ brailleArray.push('⠉');
744
+ }
745
+ }
746
+ } else if (constants.chartType == 'hist') {
747
+ let range = (constants.maxY - constants.minY) / 4;
748
+ let low = constants.minY + range;
749
+ let medium = low + range;
750
+ let medium_high = medium + range;
751
+ for (let i = 0; i &lt; plot.plotData.length; i++) {
752
+ if (plot.plotData[i].y &lt;= low) {
753
+ brailleArray.push('⣀');
754
+ } else if (plot.plotData[i].y &lt;= medium) {
755
+ brailleArray.push('⠤');
756
+ } else if (plot.plotData[i].y &lt;= medium_high) {
757
+ brailleArray.push('⠒');
758
+ } else {
759
+ brailleArray.push('⠉');
760
+ }
761
+ }
762
+ } else if (constants.chartType == 'box' &amp;&amp; position.y > -1) {
763
+ // Idea here is to use different braille characters to physically represent the box
764
+ // if sections are longer or shorter we'll add more characters
765
+ // example: outlier, small space, long min, med 25/50/75, short max: ⠂ ⠒⠒⠒⠒⠒⠒⠿⠸⠿⠒
766
+ //
767
+ // So, we get weighted lengths of each section (or gaps between outliers, etc),
768
+ // and then create the appropriate number of characters
769
+ // Full explanation on readme
770
+ //
771
+ // This is messy and long (250 lines). If anyone wants to improve, be my guest
772
+
773
+ // Some init stuff
774
+ let plotPos;
775
+ let globalMin;
776
+ let globalMax;
777
+ let numSections = plot.sections.length;
778
+ if (constants.plotOrientation == 'vert') {
779
+ plotPos = position.x;
780
+ globalMin = constants.minY;
781
+ globalMax = constants.maxY;
782
+ } else {
783
+ plotPos = position.y;
784
+ globalMin = constants.minX;
785
+ globalMax = constants.maxX;
786
+ }
787
+
788
+ // We convert main plot data to array of values and types, including min and max, and seperating outliers and removing nulls
789
+ let valData = [];
790
+ valData.push({ type: 'global_min', value: globalMin });
791
+ for (let i = 0; i &lt; numSections; i++) {
792
+ let sectionKey = plot.sections[i];
793
+ let point = plot.plotData[plotPos][sectionKey];
794
+ let charData = {};
795
+
796
+ if (point != null) {
797
+ if (sectionKey == 'lower_outlier' || sectionKey == 'upper_outlier') {
798
+ for (let j = 0; j &lt; point.length; j++) {
799
+ charData = {
800
+ type: sectionKey,
801
+ value: point[j],
802
+ };
803
+ valData.push(charData);
804
+ }
805
+ } else {
806
+ charData = {
807
+ type: sectionKey,
808
+ value: point,
809
+ };
810
+ valData.push(charData);
811
+ }
812
+ }
813
+ }
814
+ valData.push({ type: 'global_max', value: globalMax });
815
+
816
+ // Then we convert to lengths and types
817
+ // We assign lengths based on the difference between each point, and assign blanks if this comes before or after an outlier
818
+ let lenData = [];
819
+ let isBeforeMid = true;
820
+ for (let i = 0; i &lt; valData.length; i++) {
821
+ let diff;
822
+ // we compare inwardly, and midpoint is len 0
823
+ if (isBeforeMid) {
824
+ diff = Math.abs(valData[i + 1].value - valData[i].value);
825
+ } else {
826
+ diff = Math.abs(valData[i].value - valData[i - 1].value);
827
+ }
828
+
829
+ if (
830
+ valData[i].type == 'global_min' ||
831
+ valData[i].type == 'global_max'
832
+ ) {
833
+ lenData.push({ type: 'blank', length: diff });
834
+ } else if (valData[i].type == 'lower_outlier') {
835
+ // add diff as space, as well as a 0 len outlier point
836
+ // add blank last, as the earlier point is covered by global_min
837
+ lenData.push({ type: valData[i].type, length: 0 });
838
+ lenData.push({ type: 'blank', length: diff });
839
+ } else if (valData[i].type == 'upper_outlier') {
840
+ // add diff as space, as well as a 0 len outlier point, but reverse order from lower_outlier obvs
841
+ lenData.push({ type: 'blank', length: diff });
842
+ lenData.push({ type: valData[i].type, length: 0 });
843
+ } else if (valData[i].type == 'q2') {
844
+ // change calc method after midpoint, as we want spacing to go outward from center (and so center has no length)
845
+ isBeforeMid = false;
846
+ lenData.push({ type: valData[i].type, length: 0 });
847
+ } else {
848
+ // normal points
849
+ lenData.push({ type: valData[i].type, length: diff });
850
+ }
851
+ }
852
+
853
+ // We create a set of braille characters based on the lengths
854
+
855
+ // Method:
856
+ // We normalize the lengths of each characters needed length
857
+ // by the total number of characters we have availble
858
+ // (including offset from characters requiring 1 character).
859
+ // Then apply the appropriate number of characters to each
860
+
861
+ // A few exceptions:
862
+ // exception: each must have min 1 character (not blanks or length 0)
863
+ // exception: for 25/75 and min/max, if they aren't exactly equal, assign different num characters
864
+ // exception: center is always 456 123
865
+
866
+ // Step 1, sorta init.
867
+ // We prepopulate each non null section with a single character, and log for character offset
868
+ let locMin = -1;
869
+ let locQ1 = -1;
870
+ let locQ3 = -1;
871
+ let locMax = -1;
872
+ let numAllocatedChars = 0; // counter for number of characters we've already assigned
873
+ for (let i = 0; i &lt; lenData.length; i++) {
874
+ if (
875
+ lenData[i].type != 'blank' &amp;&amp;
876
+ (lenData[i].length > 0 ||
877
+ lenData[i].type == 'lower_outlier' ||
878
+ lenData[i].type == 'upper_outlier')
879
+ ) {
880
+ lenData[i].numChars = 1;
881
+ numAllocatedChars++;
882
+ } else {
883
+ lenData[i].numChars = 0;
884
+ }
885
+
886
+ // store 25/75 min/max locations so we can check them later more easily
887
+ if (lenData[i].type == 'min' &amp;&amp; lenData[i].length > 0) locMin = i;
888
+ if (lenData[i].type == 'max' &amp;&amp; lenData[i].length > 0) locMax = i;
889
+ if (lenData[i].type == 'q1') locQ1 = i;
890
+ if (lenData[i].type == 'q3') locQ3 = i;
891
+
892
+ // 50 gets 2 characters by default
893
+ if (lenData[i].type == 'q2') {
894
+ lenData[i].numChars = 2;
895
+ numAllocatedChars++; // we just ++ here as we already ++'d above
896
+ }
897
+ }
898
+
899
+ // make sure rules are set for pairs (q1 / q3, min / max)
900
+ // if they're equal length, we don't need to do anything as they already each have 1 character
901
+ // if they're not equal length, we need to add 1 character to the longer one
902
+ if (locMin > -1 &amp;&amp; locMax > -1) {
903
+ // we do it this way as we don't always have both min and max
904
+
905
+ if (lenData[locMin].length != lenData[locMax].length) {
906
+ if (lenData[locMin].length > lenData[locMax].length) {
907
+ lenData[locMin].numChars++;
908
+ numAllocatedChars++;
909
+ } else {
910
+ lenData[locMax].numChars++;
911
+ numAllocatedChars++;
912
+ }
913
+ }
914
+ }
915
+ // same for q1/q3
916
+ if (lenData[locQ1].length != lenData[locQ3].length) {
917
+ if (lenData[locQ1].length > lenData[locQ3].length) {
918
+ lenData[locQ1].numChars++;
919
+ numAllocatedChars++;
920
+ } else {
921
+ lenData[locQ3].numChars++;
922
+ numAllocatedChars++;
923
+ }
924
+ }
925
+
926
+ // Step 2: normalize and allocate remaining characters and add to our main braille array
927
+ let charsAvailable = constants.brailleDisplayLength - numAllocatedChars;
928
+ let allocateCharacters = this.AllocateCharacters(lenData, charsAvailable);
929
+ // apply allocation
930
+ let brailleData = lenData;
931
+ for (let i = 0; i &lt; allocateCharacters.length; i++) {
932
+ if (allocateCharacters[i]) {
933
+ brailleData[i].numChars += allocateCharacters[i];
934
+ }
935
+ }
936
+
937
+ constants.brailleData = brailleData;
938
+ if (constants.debugLevel > 5) {
939
+ console.log('plotData[i]', plot.plotData[plotPos]);
940
+ console.log('valData', valData);
941
+ console.log('lenData', lenData);
942
+ console.log('brailleData', brailleData);
943
+ }
944
+
945
+ // convert to braille characters
946
+ for (let i = 0; i &lt; brailleData.length; i++) {
947
+ for (let j = 0; j &lt; brailleData[i].numChars; j++) {
948
+ let brailleChar = '⠀'; // blank
949
+ if (brailleData[i].type == 'min' || brailleData[i].type == 'max') {
950
+ brailleChar = '⠒';
951
+ } else if (
952
+ brailleData[i].type == 'q1' ||
953
+ brailleData[i].type == 'q3'
954
+ ) {
955
+ brailleChar = '⠿';
956
+ } else if (brailleData[i].type == 'q2') {
957
+ if (j == 0) {
958
+ brailleChar = '⠸';
959
+ } else {
960
+ brailleChar = '⠇';
961
+ }
962
+ } else if (
963
+ brailleData[i].type == 'lower_outlier' ||
964
+ brailleData[i].type == 'upper_outlier'
965
+ ) {
966
+ brailleChar = '⠂';
967
+ }
968
+ brailleArray.push(brailleChar);
969
+ }
970
+ }
971
+ } else if (constants.chartType == 'line') {
972
+ // TODO
973
+ // ⠑
974
+ let range = (constants.maxY - constants.minY) / 4;
975
+ let low = constants.minY + range;
976
+ let medium = low + range;
977
+ let medium_high = medium + range;
978
+ let high = medium_high + range;
979
+
980
+ for (let i = 0; i &lt; plot.pointValuesY.length; i++) {
981
+ if (
982
+ plot.pointValuesY[i] &lt;= low &amp;&amp;
983
+ i - 1 >= 0 &amp;&amp;
984
+ plot.pointValuesY[i - 1] > low
985
+ ) {
986
+ // move from higher ranges to low
987
+ if (plot.pointValuesY[i - 1] &lt;= medium) {
988
+ // move away from medium range
989
+ brailleArray.push('⢄');
990
+ } else if (plot.pointValuesY[i - 1] &lt;= medium_high) {
991
+ // move away from medium high range
992
+ brailleArray.push('⢆');
993
+ } else if (plot.pointValuesY[i - 1] > medium_high) {
994
+ // move away from high range
995
+ brailleArray.push('⢇');
996
+ }
997
+ } else if (plot.pointValuesY[i] &lt;= low) {
998
+ // in the low range
999
+ brailleArray.push('⣀');
1000
+ } else if (i - 1 >= 0 &amp;&amp; plot.pointValuesY[i - 1] &lt;= low) {
1001
+ // move from low to higher ranges
1002
+ if (plot.pointValuesY[i] &lt;= medium) {
1003
+ // move to medium range
1004
+ brailleArray.push('⡠');
1005
+ } else if (plot.pointValuesY[i] &lt;= medium_high) {
1006
+ // move to medium high range
1007
+ brailleArray.push('⡰');
1008
+ } else if (plot.pointValuesY[i] > medium_high) {
1009
+ // move to high range
1010
+ brailleArray.push('⡸');
1011
+ }
1012
+ } else if (
1013
+ plot.pointValuesY[i] &lt;= medium &amp;&amp;
1014
+ i - 1 >= 0 &amp;&amp;
1015
+ plot.pointValuesY[i - 1] > medium
1016
+ ) {
1017
+ if (plot.pointValuesY[i - 1] &lt;= medium_high) {
1018
+ // move away from medium high range to medium
1019
+ brailleArray.push('⠢');
1020
+ } else if (plot.pointValuesY[i - 1] > medium_high) {
1021
+ // move away from high range
1022
+ brailleArray.push('⠣');
1023
+ }
1024
+ } else if (plot.pointValuesY[i] &lt;= medium) {
1025
+ brailleArray.push('⠤');
1026
+ } else if (i - 1 >= 0 &amp;&amp; plot.pointValuesY[i - 1] &lt;= medium) {
1027
+ // move from medium to higher ranges
1028
+ if (plot.pointValuesY[i] &lt;= medium_high) {
1029
+ // move to medium high range
1030
+ brailleArray.push('⠔');
1031
+ } else if (plot.pointValuesY[i] > medium_high) {
1032
+ // move to high range
1033
+ brailleArray.push('⠜');
1034
+ }
1035
+ } else if (
1036
+ plot.pointValuesY[i] &lt;= medium_high &amp;&amp;
1037
+ i - 1 >= 0 &amp;&amp;
1038
+ plot.pointValuesY[i - 1] > medium_high
1039
+ ) {
1040
+ // move away from high range to medium high
1041
+ brailleArray.push('⠑');
1042
+ } else if (plot.pointValuesY[i] &lt;= medium_high) {
1043
+ brailleArray.push('⠒');
1044
+ } else if (i - 1 >= 0 &amp;&amp; plot.pointValuesY[i - 1] &lt;= medium_high) {
1045
+ // move from medium high to high range
1046
+ brailleArray.push('⠊');
1047
+ } else if (plot.pointValuesY[i] &lt;= high) {
1048
+ brailleArray.push('⠉');
1049
+ }
1050
+ }
1051
+ }
1052
+
1053
+ constants.brailleInput.value = brailleArray.join('');
1054
+
1055
+ constants.brailleInput.value = brailleArray.join('');
1056
+ if (constants.debugLevel > 5) {
1057
+ console.log('braille:', constants.brailleInput.value);
1058
+ }
1059
+
1060
+ this.UpdateBraillePos();
1061
+ }
1062
+
1063
+ CharLenImpact(charData) {
1064
+ return charData.length / charData.numChars;
1065
+ }
1066
+
1067
+ /**
1068
+ * This function allocates a total number of characters among an array of lengths,
1069
+ * proportionally to each length.
1070
+ *
1071
+ * @param {Array} arr - The array of objects containing lengths, type, and current numChars. Each length should be a positive number.
1072
+ * @param {number} charsToAllocate - The total number of characters to be allocated.
1073
+ *
1074
+ * The function first calculates the sum of all lengths in the array. Then, it
1075
+ * iterates over the array and calculates an initial allocation for each length,
1076
+ * rounded to the nearest integer, based on its proportion of the total length.
1077
+ *
1078
+ * If the sum of these initial allocations is not equal to the total number of
1079
+ * characters due to rounding errors, the function makes adjustments to the allocations.
1080
+ *
1081
+ * The adjustments are made in a loop that continues until the difference between
1082
+ * the total number of characters and the sum of the allocations is zero, or until
1083
+ * the loop has run a maximum number of iterations equal to the length of the array.
1084
+ *
1085
+ * In each iteration of the loop, the function calculates a rounding adjustment for
1086
+ * each length, again based on its proportion of the total length, and adds this
1087
+ * adjustment to the length's allocation.
1088
+ *
1089
+ * If there's still a difference after the maximum number of iterations, the function
1090
+ * falls back to a simpler method of distributing the difference: it sorts the lengths
1091
+ * by their allocations and adds or subtracts 1 from each length in this order until
1092
+ * the difference is zero.
1093
+ *
1094
+ * The function returns an array of the final allocations.
1095
+ *
1096
+ * @returns {Array} The array of allocations.
1097
+ */
1098
+ AllocateCharacters(arr, charsToAllocate) {
1099
+ // init
1100
+ let allocation = [];
1101
+ let sumLen = 0;
1102
+ for (let i = 0; i &lt; arr.length; i++) {
1103
+ sumLen += arr[i].length;
1104
+ }
1105
+ let notAllowed = ['lower_outlier', 'upper_outlier', '50']; // these types only have the 1 char they were assigned above
1106
+
1107
+ // main allocation
1108
+ for (let i = 0; i &lt; arr.length; i++) {
1109
+ if (!notAllowed.includes(arr[i].type)) {
1110
+ allocation[i] = Math.round((arr[i].length / sumLen) * charsToAllocate);
1111
+ }
1112
+ }
1113
+
1114
+ // main allocation is not perfect, so we need to adjust
1115
+ let allocatedSum = allocation.reduce((a, b) => a + b, 0);
1116
+ let difference = charsToAllocate - allocatedSum;
1117
+
1118
+ // If there's a rounding error, add/subtract characters proportionally
1119
+ let maxIterations = arr.length; // inf loop handler :D
1120
+ while (difference !== 0 &amp;&amp; maxIterations > 0) {
1121
+ // (same method as above)
1122
+ for (let i = 0; i &lt; arr.length; i++) {
1123
+ if (!notAllowed.includes(arr[i].type)) {
1124
+ allocation[i] += Math.round((arr[i].length / sumLen) * difference);
1125
+ }
1126
+ }
1127
+ allocatedSum = allocation.reduce((a, b) => a + b, 0);
1128
+ difference = charsToAllocate - allocatedSum;
1129
+
1130
+ maxIterations--;
1131
+ }
1132
+
1133
+ // if there's still a rounding error after max iterations, fuck it, just distribute it evenly
1134
+ if (difference !== 0) {
1135
+ // create an array of indices sorted low to high based on current allocations
1136
+ let indices = [];
1137
+ for (let i = 0; i &lt; arr.length; i++) {
1138
+ indices.push(i);
1139
+ }
1140
+ indices.sort((a, b) => allocation[a] - allocation[b]);
1141
+
1142
+ // if we need to add or remove characters, do so from the beginning
1143
+ let plusminus = -1; // add or remove?
1144
+ if (difference > 0) {
1145
+ plusminus = 1;
1146
+ }
1147
+ let i = 0;
1148
+ let maxIterations = indices.length * 3; // run it for a while just in case
1149
+ while (difference > 0 &amp;&amp; maxIterations > 0) {
1150
+ allocation[indices[i]] += plusminus;
1151
+ difference += -plusminus;
1152
+
1153
+ i += 1;
1154
+ // loop back to start if we end
1155
+ if (i >= indices.length) {
1156
+ i = 0;
1157
+ }
1158
+
1159
+ maxIterations += -1;
1160
+ }
1161
+ }
1162
+
1163
+ return allocation;
1164
+ }
1165
+ }
1166
+ </code></pre>
1167
+ </article>
1168
+ </section>
1169
+
1170
+
1171
+
1172
+
1173
+ </div>
1174
+
1175
+ <br class="clear">
1176
+
1177
+ <footer>
1178
+ Generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Sun Nov 05 2023 14:27:04 GMT-0600 (Central Standard Time) using the Minami theme.
1179
+ </footer>
1180
+
1181
+ <script>prettyPrint();</script>
1182
+ <script src="scripts/linenumber.js"></script>
1183
+ </body>
1184
+ </html>