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