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,682 @@
1
+ //
2
+ // BoxPlot class.
3
+ // This initializes and contains the JSON data model for this chart
4
+ //
5
+ // todo:
6
+ class BoxPlot {
7
+ constructor() {
8
+ constants.plotOrientation = 'horz'; // default
9
+ this.sections = [
10
+ 'lower_outlier',
11
+ 'min',
12
+ 'q1',
13
+ 'q2',
14
+ 'q3',
15
+ 'max',
16
+ 'upper_outlier',
17
+ ];
18
+
19
+ if ('axes' in singleMaidr) {
20
+ if ('x' in singleMaidr.axes) {
21
+ if ('level' in singleMaidr.axes.x) {
22
+ constants.plotOrientation = 'vert';
23
+ }
24
+ }
25
+ }
26
+
27
+ // title
28
+ this.title = '';
29
+ if ('labels' in singleMaidr) {
30
+ if ('title' in singleMaidr.labels) {
31
+ this.title = singleMaidr.labels.title;
32
+ }
33
+ }
34
+ if (this.title == '') {
35
+ if ('title' in singleMaidr) {
36
+ this.title = singleMaidr.title;
37
+ }
38
+ }
39
+ // subtitle
40
+ this.subtitle = '';
41
+ if ('labels' in singleMaidr) {
42
+ if ('subtitle' in singleMaidr.labels) {
43
+ this.subtitle = singleMaidr.labels.subtitle;
44
+ }
45
+ }
46
+ // caption
47
+ this.caption = '';
48
+ if ('labels' in singleMaidr) {
49
+ if ('caption' in singleMaidr.labels) {
50
+ this.caption = singleMaidr.labels.caption;
51
+ }
52
+ }
53
+
54
+ // axes labels
55
+ if ('labels' in singleMaidr) {
56
+ if (!this.x_group_label) {
57
+ if ('x' in singleMaidr.labels) {
58
+ this.x_group_label = singleMaidr.labels.x;
59
+ }
60
+ }
61
+ if (!this.y_group_label) {
62
+ if ('y' in singleMaidr.labels) {
63
+ this.y_group_label = singleMaidr.labels.y;
64
+ }
65
+ }
66
+ }
67
+ if ('axes' in singleMaidr) {
68
+ if ('x' in singleMaidr.axes) {
69
+ if ('label' in singleMaidr.axes.x) {
70
+ if (!this.x_group_label) {
71
+ this.x_group_label = singleMaidr.axes.x.label;
72
+ }
73
+ }
74
+ if ('level' in singleMaidr.axes.x) {
75
+ this.x_labels = singleMaidr.axes.x.level;
76
+ } else {
77
+ this.x_labels = [];
78
+ }
79
+ }
80
+ if ('y' in singleMaidr.axes) {
81
+ if ('label' in singleMaidr.axes.y) {
82
+ if (!this.y_group_label) {
83
+ this.y_group_label = singleMaidr.axes.y.label;
84
+ }
85
+ }
86
+ if ('level' in singleMaidr.axes.y) {
87
+ this.y_labels = singleMaidr.axes.y.level;
88
+ } else {
89
+ this.y_labels = [];
90
+ }
91
+ }
92
+ }
93
+
94
+ // main data
95
+ this.plotData = singleMaidr.data;
96
+
97
+ // bounds data
98
+ if ('elements' in singleMaidr) {
99
+ this.plotBounds = this.GetPlotBounds();
100
+ constants.hasRect = true;
101
+ } else {
102
+ constants.hasRect = false;
103
+ }
104
+
105
+ this.CleanData();
106
+ }
107
+
108
+ CleanData() {
109
+ // clean up data and extra vars like min / max stuff
110
+
111
+ let min, max;
112
+ for (let i = 0; i < this.plotData.length; i++) {
113
+ if (this.plotData[i].lower_outlier) {
114
+ let outlierMin = Math.min(...this.plotData[i].lower_outlier);
115
+ let outlierMax = Math.max(...this.plotData[i].lower_outlier);
116
+
117
+ if (min == undefined || outlierMin < min) min = outlierMin;
118
+ if (max == undefined || outlierMax > max) max = outlierMax;
119
+ }
120
+ if (this.plotData[i].min) {
121
+ if (min == undefined || this.plotData[i].min < min)
122
+ min = this.plotData[i].min;
123
+ if (max == undefined || this.plotData[i].max > max)
124
+ max = this.plotData[i].max;
125
+ }
126
+ if (this.plotData[i].q1) {
127
+ if (min == undefined || this.plotData[i].q1 < min)
128
+ min = this.plotData[i].q1;
129
+ if (max == undefined || this.plotData[i].q1 > max)
130
+ max = this.plotData[i].q1;
131
+ }
132
+ if (this.plotData[i].q2) {
133
+ if (min == undefined || this.plotData[i].q2 < min)
134
+ min = this.plotData[i].q2;
135
+ if (max == undefined || this.plotData[i].q2 > max)
136
+ max = this.plotData[i].q2;
137
+ }
138
+ if (this.plotData[i].q3) {
139
+ if (min == undefined || this.plotData[i].q3 < min)
140
+ min = this.plotData[i].q3;
141
+ if (max == undefined || this.plotData[i].q3 > max)
142
+ max = this.plotData[i].q3;
143
+ }
144
+ if (this.plotData[i].max) {
145
+ if (min == undefined || this.plotData[i].max < min)
146
+ min = this.plotData[i].max;
147
+ if (max == undefined || this.plotData[i].max > max)
148
+ max = this.plotData[i].max;
149
+ }
150
+ if (this.plotData[i].upper_outlier) {
151
+ let outlierMin = Math.min(...this.plotData[i].upper_outlier);
152
+ let outlierMax = Math.max(...this.plotData[i].upper_outlier);
153
+
154
+ if (min == undefined || outlierMin < min) min = outlierMin;
155
+ if (max == undefined || outlierMax > max) max = outlierMax;
156
+ }
157
+ }
158
+
159
+ if (constants.plotOrientation == 'vert') {
160
+ constants.minY = min;
161
+ constants.maxY = max;
162
+ constants.minX = 0;
163
+ constants.maxX = this.plotData.length - 1;
164
+ } else {
165
+ constants.minX = min;
166
+ constants.maxX = max;
167
+ constants.minY = 0;
168
+ constants.maxY = this.plotData.length - 1;
169
+ }
170
+ constants.autoPlayRate = Math.min(
171
+ Math.ceil(constants.AUTOPLAY_DURATION / this.plotData.length),
172
+ constants.MAX_SPEED
173
+ );
174
+ constants.DEFAULT_SPEED = constants.autoPlayRate;
175
+ if (constants.autoPlayRate < constants.MIN_SPEED) {
176
+ constants.MIN_SPEED = constants.autoPlayRate;
177
+ }
178
+ }
179
+
180
+ GetPlotBounds() {
181
+ // we fetch the elements in our parent,
182
+ // and similar to old GetData we run through and get bounding boxes (or blanks) for everything,
183
+ // and store in an identical structure
184
+
185
+ let plotBounds = [];
186
+ let allWeNeed = this.GetAllSegmentTypes();
187
+ let re = /(?:\d+(?:\.\d*)?|\.\d+)/g;
188
+
189
+ // get initial set of elements, a parent element for all outliers, whiskers, and range
190
+ let initialElemSet = [];
191
+ let plots = singleMaidr.elements.children;
192
+ for (let i = 0; i < plots.length; i++) {
193
+ // each plot
194
+ let plotSet = {};
195
+ let sections = plots[i].children;
196
+ for (let j = 0; j < sections.length; j++) {
197
+ let elemType = this.GetBoxplotSegmentType(
198
+ sections[j].getAttribute('id')
199
+ );
200
+ plotSet[elemType] = sections[j];
201
+ }
202
+ initialElemSet.push(plotSet);
203
+ }
204
+
205
+ // we build our structure based on the full set we need, and have blanks as placeholders
206
+ // many of these overlap or are missing, so now we go through and make the actual array structure we need
207
+ // like, all outliers are in 1 set, so we have to split those out and then get the bounding boxes
208
+ for (let i = 0; i < initialElemSet.length; i++) {
209
+ let plotBound = [];
210
+
211
+ // we always have a range, and need those bounds to set others, so we'll do this first
212
+ let rangeBounds = initialElemSet[i].range.getBoundingClientRect();
213
+
214
+ // we get the midpoint from actual point values in the chart GRID.segments
215
+ let midPoints = initialElemSet[i].range
216
+ .querySelector('polyline[id^="GRID"]')
217
+ .getAttribute('points')
218
+ .match(re);
219
+ let rangePoints = initialElemSet[i].range
220
+ .querySelector('polygon[id^="geom_polygon"]')
221
+ .getAttribute('points')
222
+ .match(re);
223
+ // get midpoint as percentage from bottom to mid to apply to bounding boxes
224
+ // vert: top(rangePoints[1]) | mid(midPoints[1]) | bottom(rangePoints[3])
225
+ // horz: top(rangePoints[0]) | mid(midPoints[0]) | bottom(rangePoints[2])
226
+ let midPercent = 0;
227
+ if (constants.plotOrientation == 'vert') {
228
+ midPercent =
229
+ (midPoints[1] - rangePoints[3]) / (rangePoints[1] - rangePoints[3]);
230
+ } else {
231
+ midPercent =
232
+ (midPoints[0] - rangePoints[2]) / (rangePoints[0] - rangePoints[2]);
233
+ }
234
+ let midSize = 0;
235
+ if (constants.plotOrientation == 'vert') {
236
+ midSize = rangeBounds.height * midPercent;
237
+ } else {
238
+ midSize = rangeBounds.width * midPercent;
239
+ }
240
+
241
+ // set bounding box values
242
+ // we critically need x / left, y / top, width, height. We can ignore the rest or let it be wrong
243
+
244
+ // 25%
245
+ plotBound[2] = this.convertBoundingClientRectToObj(rangeBounds);
246
+ plotBound[2].label = allWeNeed[2];
247
+ plotBound[2].type = 'range';
248
+ if (constants.plotOrientation == 'vert') {
249
+ plotBound[2].height = midSize;
250
+ plotBound[2].top = plotBound[2].bottom - midSize;
251
+ plotBound[2].y = plotBound[2].top;
252
+ } else {
253
+ plotBound[2].width = midSize;
254
+ }
255
+ // 50%
256
+ plotBound[3] = this.convertBoundingClientRectToObj(rangeBounds);
257
+ plotBound[3].label = allWeNeed[3];
258
+ plotBound[3].type = 'range';
259
+ if (constants.plotOrientation == 'vert') {
260
+ plotBound[3].height = 0;
261
+ plotBound[3].top = rangeBounds.bottom - midSize;
262
+ plotBound[3].y = plotBound[3].top;
263
+ plotBound[3].bottom = plotBound[3].top;
264
+ } else {
265
+ plotBound[3].width = 0;
266
+ plotBound[3].left = rangeBounds.left + midSize;
267
+ }
268
+ // 75%
269
+ plotBound[4] = this.convertBoundingClientRectToObj(rangeBounds);
270
+ plotBound[4].label = allWeNeed[4];
271
+ plotBound[4].type = 'range';
272
+ if (constants.plotOrientation == 'vert') {
273
+ plotBound[4].height = rangeBounds.height - midSize;
274
+ plotBound[4].bottom = plotBound[3].top;
275
+ } else {
276
+ plotBound[4].width = rangeBounds.width - midSize;
277
+ plotBound[4].left = plotBound[3].left;
278
+ }
279
+
280
+ // now the tricky ones, outliers and whiskers, if we have them
281
+ if (Object.hasOwn(initialElemSet[i], 'whisker')) {
282
+ // ok great we have a whisker. It could be just above or below or span across the range (in which case we need to split it up). Let's check
283
+ let whiskerBounds = initialElemSet[i].whisker.getBoundingClientRect();
284
+ let hasBelow = false;
285
+ let hasAbove = false;
286
+ if (constants.plotOrientation == 'vert') {
287
+ if (whiskerBounds.bottom > rangeBounds.bottom) hasBelow = true;
288
+ if (whiskerBounds.top < rangeBounds.top) hasAbove = true;
289
+ } else {
290
+ if (whiskerBounds.left < rangeBounds.left) hasBelow = true;
291
+ if (whiskerBounds.right > rangeBounds.right) hasAbove = true;
292
+ }
293
+
294
+ // lower whisker
295
+ if (hasBelow) {
296
+ plotBound[1] = this.convertBoundingClientRectToObj(whiskerBounds);
297
+ plotBound[1].label = allWeNeed[1];
298
+ plotBound[1].type = 'whisker';
299
+ if (constants.plotOrientation == 'vert') {
300
+ plotBound[1].top = plotBound[2].bottom;
301
+ plotBound[1].y = plotBound[1].top;
302
+ plotBound[1].height = plotBound[1].bottom - plotBound[1].top;
303
+ } else {
304
+ plotBound[1].width = plotBound[2].left - plotBound[1].left;
305
+ }
306
+ } else {
307
+ plotBound[1] = {};
308
+ plotBound[1].label = allWeNeed[1];
309
+ plotBound[1].type = 'blank';
310
+ }
311
+ // upper whisker
312
+ if (hasAbove) {
313
+ plotBound[5] = this.convertBoundingClientRectToObj(whiskerBounds);
314
+ plotBound[5].label = allWeNeed[5];
315
+ plotBound[5].type = 'whisker';
316
+ if (constants.plotOrientation == 'vert') {
317
+ plotBound[5].bottom = plotBound[4].top;
318
+ plotBound[5].height = plotBound[5].bottom - plotBound[5].top;
319
+ } else {
320
+ plotBound[5].left = plotBound[4].right;
321
+ plotBound[5].x = plotBound[4].right;
322
+ plotBound[5].width = plotBound[5].right - plotBound[5].left;
323
+ }
324
+ } else {
325
+ plotBound[5] = {};
326
+ plotBound[5].label = allWeNeed[5];
327
+ plotBound[5].type = 'blank';
328
+ }
329
+ }
330
+ if (Object.hasOwn(initialElemSet[i], 'outlier')) {
331
+ // we have one or more outliers.
332
+ // Where do they appear? above or below the range? both?
333
+ // we want to split them up and put 1 bounding box around each above and below
334
+
335
+ let outlierElems = initialElemSet[i].outlier.children;
336
+ let outlierUpperBounds = null;
337
+ let outlierLowerBounds = null;
338
+ for (let j = 0; j < outlierElems.length; j++) {
339
+ // add this outlier's bounds, or expand if more than one
340
+ let newOutlierBounds = outlierElems[j].getBoundingClientRect();
341
+
342
+ if (constants.plotOrientation == 'vert') {
343
+ if (newOutlierBounds.y < rangeBounds.y) {
344
+ // higher, remember y=0 is at the bottom of the page
345
+ if (!outlierUpperBounds) {
346
+ outlierUpperBounds =
347
+ this.convertBoundingClientRectToObj(newOutlierBounds);
348
+ } else {
349
+ if (newOutlierBounds.y < outlierUpperBounds.y)
350
+ outlierUpperBounds.y = newOutlierBounds.y;
351
+ if (newOutlierBounds.top < outlierUpperBounds.top)
352
+ outlierUpperBounds.top = newOutlierBounds.top;
353
+ if (newOutlierBounds.bottom > outlierUpperBounds.bottom)
354
+ outlierUpperBounds.bottom = newOutlierBounds.bottom;
355
+ }
356
+ } else {
357
+ if (!outlierLowerBounds) {
358
+ outlierLowerBounds =
359
+ this.convertBoundingClientRectToObj(newOutlierBounds);
360
+ } else {
361
+ if (newOutlierBounds.y < outlierLowerBounds.y)
362
+ outlierLowerBounds.y = newOutlierBounds.y;
363
+ if (newOutlierBounds.top < outlierLowerBounds.top)
364
+ outlierLowerBounds.top = newOutlierBounds.top;
365
+ if (newOutlierBounds.bottom > outlierLowerBounds.bottom)
366
+ outlierLowerBounds.bottom = newOutlierBounds.bottom;
367
+ }
368
+ }
369
+ } else {
370
+ if (newOutlierBounds.x > rangeBounds.x) {
371
+ // higher, remember x=0 is at the left of the page
372
+ if (!outlierUpperBounds) {
373
+ outlierUpperBounds =
374
+ this.convertBoundingClientRectToObj(newOutlierBounds);
375
+ } else {
376
+ if (newOutlierBounds.x < outlierUpperBounds.x)
377
+ outlierUpperBounds.x = newOutlierBounds.x;
378
+ if (newOutlierBounds.left < outlierUpperBounds.left)
379
+ outlierUpperBounds.left = newOutlierBounds.left;
380
+ if (newOutlierBounds.right > outlierUpperBounds.right)
381
+ outlierUpperBounds.right = newOutlierBounds.right;
382
+ }
383
+ } else {
384
+ if (!outlierLowerBounds) {
385
+ outlierLowerBounds =
386
+ this.convertBoundingClientRectToObj(newOutlierBounds);
387
+ } else {
388
+ if (newOutlierBounds.x < outlierLowerBounds.x)
389
+ outlierLowerBounds.x = newOutlierBounds.x;
390
+ if (newOutlierBounds.left < outlierLowerBounds.left)
391
+ outlierLowerBounds.left = newOutlierBounds.left;
392
+ if (newOutlierBounds.right > outlierLowerBounds.right)
393
+ outlierLowerBounds.right = newOutlierBounds.right;
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ // now we add plotBound outlier stuff
400
+ if (outlierLowerBounds) {
401
+ outlierLowerBounds.height =
402
+ outlierLowerBounds.bottom - outlierLowerBounds.top;
403
+ outlierLowerBounds.width =
404
+ outlierLowerBounds.right - outlierLowerBounds.left;
405
+
406
+ plotBound[0] =
407
+ this.convertBoundingClientRectToObj(outlierLowerBounds);
408
+ plotBound[0].label = allWeNeed[0];
409
+ plotBound[0].type = 'outlier';
410
+ } else {
411
+ plotBound[0] = {};
412
+ plotBound[0].label = allWeNeed[0];
413
+ plotBound[0].type = 'blank';
414
+ }
415
+ if (outlierUpperBounds) {
416
+ outlierUpperBounds.height =
417
+ outlierUpperBounds.bottom - outlierUpperBounds.top;
418
+ outlierUpperBounds.width =
419
+ outlierUpperBounds.right - outlierUpperBounds.left;
420
+
421
+ plotBound[6] =
422
+ this.convertBoundingClientRectToObj(outlierUpperBounds);
423
+ plotBound[6].label = allWeNeed[6];
424
+ plotBound[6].type = 'outlier';
425
+ } else {
426
+ plotBound[6] = {};
427
+ plotBound[6].label = allWeNeed[6];
428
+ plotBound[6].type = 'blank';
429
+ }
430
+ } else {
431
+ // add all blanks
432
+ plotBound[0] = {};
433
+ plotBound[0].label = allWeNeed[0];
434
+ plotBound[0].type = 'blank';
435
+ plotBound[6] = {};
436
+ plotBound[6].label = allWeNeed[6];
437
+ plotBound[6].type = 'blank';
438
+ }
439
+
440
+ plotBounds.push(plotBound);
441
+ }
442
+
443
+ if (constants.debugLevel > 5) {
444
+ console.log('plotBounds', plotBounds);
445
+ }
446
+
447
+ return plotBounds;
448
+ }
449
+
450
+ GetAllSegmentTypes() {
451
+ let allWeNeed = [
452
+ resources.GetString('lower_outlier'),
453
+ resources.GetString('min'),
454
+ resources.GetString('25'),
455
+ resources.GetString('50'),
456
+ resources.GetString('75'),
457
+ resources.GetString('max'),
458
+ resources.GetString('upper_outlier'),
459
+ ];
460
+
461
+ return allWeNeed;
462
+ }
463
+
464
+ GetBoxplotSegmentType(sectionId) {
465
+ // Helper function for main GetData:
466
+ // Fetch type, which comes from section id:
467
+ // geom_polygon = range
468
+ // GRID = whisker
469
+ // points = outlier
470
+
471
+ let segmentType = 'outlier'; // default? todo: should probably default null, and then throw error instead of return if not set after ifs
472
+ if (sectionId.includes('geom_crossbar')) {
473
+ segmentType = 'range';
474
+ } else if (sectionId.includes('GRID')) {
475
+ segmentType = 'whisker';
476
+ } else if (sectionId.includes('points')) {
477
+ segmentType = 'outlier';
478
+ }
479
+
480
+ return segmentType;
481
+ }
482
+
483
+ GetBoxplotSegmentPoints(segment, segmentType) {
484
+ // Helper function for main GetData:
485
+ // Fetch x and y point data from chart
486
+
487
+ let re = /(?:\d+(?:\.\d*)?|\.\d+)/g;
488
+ let pointString = '';
489
+ let points = [];
490
+ if (segmentType == 'range') {
491
+ // ranges go a level deeper
492
+ let matches = segment.children[0].getAttribute('points').match(re);
493
+ points.push(matches[0], matches[1]);
494
+ // the middle bar has 2 points but we just need one, check if they're the same
495
+ if (matches[0] != matches[2]) {
496
+ points.push(matches[2], matches[3]);
497
+ }
498
+ } else if (segmentType == 'outlier') {
499
+ // outliers use x attr directly, but have multiple children
500
+ points.push(segment.getAttribute('x'), segment.getAttribute('y'));
501
+ } else {
502
+ // whisker. Get first and third number from points attr
503
+ // but sometimes it's null, giving the same for both, and don't add if that's true
504
+ let matches = segment.getAttribute('points').match(re);
505
+ if (constants.plotOrientation == 'vert') {
506
+ if (matches[1] != matches[3]) {
507
+ points.push(matches[0], matches[1], matches[2], matches[3]);
508
+ }
509
+ } else {
510
+ if (matches[0] != matches[2]) {
511
+ points.push(matches[0], matches[1], matches[2], matches[3]);
512
+ }
513
+ }
514
+ }
515
+
516
+ return points;
517
+ }
518
+ GetAllSegmentTypes() {
519
+ let allWeNeed = [
520
+ resources.GetString('lower_outlier'),
521
+ resources.GetString('min'),
522
+ resources.GetString('25'),
523
+ resources.GetString('50'),
524
+ resources.GetString('75'),
525
+ resources.GetString('max'),
526
+ resources.GetString('upper_outlier'),
527
+ ];
528
+
529
+ return allWeNeed;
530
+ }
531
+
532
+ convertBoundingClientRectToObj(rect) {
533
+ return {
534
+ top: rect.top,
535
+ right: rect.right,
536
+ bottom: rect.bottom,
537
+ left: rect.left,
538
+ width: rect.width,
539
+ height: rect.height,
540
+ x: rect.x,
541
+ y: rect.y,
542
+ };
543
+ }
544
+
545
+ PlayTones() {
546
+ // init
547
+ let plotPos = null;
548
+ let sectionKey = null;
549
+ if (constants.outlierInterval) clearInterval(constants.outlierInterval);
550
+ if (constants.plotOrientation == 'vert') {
551
+ plotPos = position.x;
552
+ sectionKey = this.GetSectionKey(position.y);
553
+ } else {
554
+ plotPos = position.y;
555
+ sectionKey = this.GetSectionKey(position.x);
556
+ }
557
+
558
+ // chose tone to play
559
+ if (plot.plotData[plotPos][sectionKey] == null) {
560
+ audio.PlayNull();
561
+ } else if (sectionKey != 'lower_outlier' && sectionKey != 'upper_outlier') {
562
+ // normal tone
563
+ audio.playTone();
564
+ } else if (plot.plotData[plotPos][sectionKey].length == 0) {
565
+ audio.PlayNull();
566
+ } else {
567
+ // outlier(s): we play a run of tones
568
+ position.z = 0;
569
+ constants.outlierInterval = setInterval(function () {
570
+ // play this tone
571
+ audio.playTone();
572
+
573
+ // and then set up for the next one
574
+ position.z += 1;
575
+
576
+ // and kill if we're done
577
+ if (plot.plotData[plotPos][sectionKey] == null) {
578
+ clearInterval(constants.outlierInterval);
579
+ position.z = -1;
580
+ } else if (position.z + 1 > plot.plotData[plotPos][sectionKey].length) {
581
+ clearInterval(constants.outlierInterval);
582
+ position.z = -1;
583
+ }
584
+ }, constants.autoPlayOutlierRate);
585
+ }
586
+ }
587
+
588
+ GetSectionKey(sectionPos) {
589
+ return this.sections[sectionPos];
590
+ }
591
+ }
592
+
593
+ // BoxplotRect class
594
+ // Initializes and updates the visual outline around sections of the chart
595
+ class BoxplotRect {
596
+ // maybe put this stuff in user config?
597
+ rectPadding = 15; // px
598
+ rectStrokeWidth = 4; // px
599
+
600
+ constructor() {
601
+ this.x1 = 0;
602
+ this.width = 0;
603
+ this.y1 = 0;
604
+ this.height = 0;
605
+ this.chartOffsetLeft = constants.chart.getBoundingClientRect().left;
606
+ this.chartOffsetTop = constants.chart.getBoundingClientRect().top;
607
+ }
608
+
609
+ UpdateRect() {
610
+ // UpdateRect takes bounding box values from the object and gets bounds of visual outline to be drawn
611
+
612
+ if (document.getElementById('highlight_rect'))
613
+ document.getElementById('highlight_rect').remove(); // destroy to be recreated
614
+
615
+ let plotPos = position.x;
616
+ let sectionPos = position.y;
617
+ let sectionKey = plot.GetSectionKey(position.y);
618
+ if (constants.plotOrientation == 'vert') {
619
+ } else {
620
+ plotPos = position.y;
621
+ sectionPos = position.x;
622
+ sectionKey = plot.GetSectionKey(position.x);
623
+ }
624
+
625
+ if (
626
+ (constants.plotOrientation == 'vert' && position.y > -1) ||
627
+ (constants.plotOrientation == 'horz' && position.x > -1)
628
+ ) {
629
+ // initial value could be -1, which throws errors, so ignore that
630
+
631
+ let bounds = plot.plotBounds[plotPos][sectionPos];
632
+
633
+ if (bounds.type != 'blank') {
634
+ //let chartBounds = constants.chart.getBoundingClientRect();
635
+
636
+ this.x1 = bounds.left - this.rectPadding - this.chartOffsetLeft;
637
+ this.width = bounds.width + this.rectPadding * 2;
638
+ this.y1 = bounds.top - this.rectPadding - this.chartOffsetTop;
639
+ this.height = bounds.height + this.rectPadding * 2;
640
+
641
+ if (constants.debugLevel > 5) {
642
+ console.log(
643
+ 'Point',
644
+ sectionKey,
645
+ 'bottom:',
646
+ bounds.bottom,
647
+ 'top:',
648
+ bounds.top
649
+ );
650
+ console.log(
651
+ 'x1:',
652
+ this.x1,
653
+ 'y1:',
654
+ this.y1,
655
+ 'width:',
656
+ this.width,
657
+ 'height:',
658
+ this.height
659
+ );
660
+ }
661
+
662
+ this.CreateRectDisplay();
663
+ }
664
+ }
665
+ }
666
+
667
+ CreateRectDisplay() {
668
+ // CreateRectDisplay takes bounding points and creates the visual outline
669
+
670
+ const svgns = 'http://www.w3.org/2000/svg';
671
+ let rect = document.createElementNS(svgns, 'rect');
672
+ rect.setAttribute('id', 'highlight_rect');
673
+ rect.setAttribute('x', this.x1);
674
+ rect.setAttribute('y', this.y1); // y coord is inverse from plot data
675
+ rect.setAttribute('width', this.width);
676
+ rect.setAttribute('height', this.height);
677
+ rect.setAttribute('stroke', constants.colorSelected);
678
+ rect.setAttribute('stroke-width', this.rectStrokeWidth);
679
+ rect.setAttribute('fill', 'none');
680
+ constants.chart.appendChild(rect);
681
+ }
682
+ }