maidr 1.2.2 → 1.3.1

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.
package/dist/maidr.js CHANGED
@@ -74,13 +74,16 @@ class Constants {
74
74
  visualBraille = false; // do we want to represent braille based on what's visually there or actually there. Like if we have 2 outliers with the same position, do we show 1 (visualBraille true) or 2 (false)
75
75
  globalMinMax = true;
76
76
  ariaMode = 'assertive'; // assertive (default) / polite
77
- playLLMWaitingSound = true;
78
77
 
79
78
  // LLM settings
80
- LLMDebugMode = 0; // 0 = use real data, 1 = all fake, 2 = real data but no image
81
- authKey = null; // OpenAI authentication key, set in menu
79
+ openAIAuthKey = null; // OpenAI authentication key, set in menu
80
+ geminiAuthKey = null; // Gemini authentication key, set in menu
82
81
  LLMmaxResponseTokens = 1000; // max tokens to send to LLM, 20 for testing, 1000 ish for real
82
+ playLLMWaitingSound = true;
83
83
  LLMDetail = 'high'; // low (default for testing, like 100 tokens) / high (default for real, like 1000 tokens)
84
+ LLMModel = 'openai'; // openai (default) / gemini
85
+ LLMSystemMessage =
86
+ 'You are a helpful assistant describing the chart to a blind person';
84
87
  skillLevel = 'basic'; // basic / intermediate / expert
85
88
  skillLevelOther = ''; // custom skill level
86
89
 
@@ -297,7 +300,7 @@ class Menu {
297
300
  <div class="modal-dialog" role="document" tabindex="0">
298
301
  <div class="modal-content">
299
302
  <div class="modal-header">
300
- <h4 class="modal-title">Menu</h4>
303
+ <h2 class="modal-title">Menu</h2>
301
304
  <button type="button" class="close" data-dismiss="modal" aria-label="Close">
302
305
  <span aria-hidden="true">&times;</span>
303
306
  </button>
@@ -406,7 +409,15 @@ class Menu {
406
409
  }><label for="aria_mode_polite">Polite</label></p>
407
410
  </fieldset></div>
408
411
  <h5 class="modal-title">LLM Settings</h5>
409
- <p><input type="password" id="chatLLM_auth_key"> <label for="chatLLM_auth_key">OpenAI Authentication Key</label></p>
412
+ <p>
413
+ <select id="LLM_model">
414
+ <option value="openai">OpenAI Vision</option>
415
+ <option value="gemini">Gemini Pro Vision</option>
416
+ </select>
417
+ <label for="LLM_model">LLM Model</label>
418
+ </p>
419
+ <p id="openai_auth_key_container" class="hidden"><input type="password" id="openai_auth_key"> <label for="openai_auth_key">OpenAI Authentication Key</label></p>
420
+ <p id="gemini_auth_key_container" class="hidden"><input type="password" id="gemini_auth_key"> <label for="gemini_auth_key">Gemini Authentication Key</label></p>
410
421
  <p>
411
422
  <select id="skill_level">
412
423
  <option value="basic">Basic</option>
@@ -416,7 +427,7 @@ class Menu {
416
427
  </select>
417
428
  <label for="skill_level">Level of skill in statistical charts</label>
418
429
  </p>
419
- <p id="skill_level_other_container" class="hidden"><input type="text" id="skill_level_other"> <label for="skill_level_other">"I have a(n) [X] understanding of statistical charts"</label></p>
430
+ <p id="skill_level_other_container" class="hidden"><input type="text" placeholder="Very basic" id="skill_level_other"> <label for="skill_level_other">Describe your level of skill in statistical charts</label></p>
420
431
  </div>
421
432
  </div>
422
433
  <div class="modal-footer">
@@ -483,6 +494,29 @@ class Menu {
483
494
  },
484
495
  ]);
485
496
 
497
+ // toggle auth key fields
498
+ constants.events.push([
499
+ document.getElementById('LLM_model'),
500
+ 'change',
501
+ function (e) {
502
+ if (e.target.value == 'openai') {
503
+ document
504
+ .getElementById('openai_auth_key_container')
505
+ .classList.remove('hidden');
506
+ document
507
+ .getElementById('gemini_auth_key_container')
508
+ .classList.add('hidden');
509
+ } else if (e.target.value == 'gemini') {
510
+ document
511
+ .getElementById('openai_auth_key_container')
512
+ .classList.add('hidden');
513
+ document
514
+ .getElementById('gemini_auth_key_container')
515
+ .classList.remove('hidden');
516
+ }
517
+ },
518
+ ]);
519
+
486
520
  // Skill level other events
487
521
  constants.events.push([
488
522
  document.getElementById('skill_level'),
@@ -560,7 +594,6 @@ class Menu {
560
594
  */
561
595
  PopulateData() {
562
596
  document.getElementById('vol').value = constants.vol;
563
- //document.getElementById('show_rect').checked = constants.showRect;
564
597
  document.getElementById('autoplay_rate').value = constants.autoPlayRate;
565
598
  document.getElementById('braille_display_length').value =
566
599
  constants.brailleDisplayLength;
@@ -569,14 +602,20 @@ class Menu {
569
602
  document.getElementById('max_freq').value = constants.MAX_FREQUENCY;
570
603
  document.getElementById('keypress_interval').value =
571
604
  constants.keypressInterval;
572
- if (typeof constants.authKey == 'string') {
573
- document.getElementById('chatLLM_auth_key').value = constants.authKey;
605
+ if (typeof constants.openAIAuthKey == 'string') {
606
+ document.getElementById('openai_auth_key').value =
607
+ constants.openAIAuthKey;
608
+ }
609
+ if (typeof constants.geminiAuthKey == 'string') {
610
+ document.getElementById('gemini_auth_key').value =
611
+ constants.geminiAuthKey;
574
612
  }
575
613
  document.getElementById('skill_level').value = constants.skillLevel;
576
614
  if (constants.skillLevelOther) {
577
615
  document.getElementById('skill_level_other').value =
578
616
  constants.skillLevelOther;
579
617
  }
618
+ document.getElementById('LLM_model').value = constants.LLMModel;
580
619
 
581
620
  // aria mode
582
621
  if (constants.ariaMode == 'assertive') {
@@ -586,6 +625,22 @@ class Menu {
586
625
  document.getElementById('aria_mode_polite').checked = true;
587
626
  document.getElementById('aria_mode_assertive').checked = false;
588
627
  }
628
+ // hide either openai or gemini auth key field
629
+ if (constants.LLMModel == 'openai') {
630
+ document
631
+ .getElementById('openai_auth_key_container')
632
+ .classList.remove('hidden');
633
+ document
634
+ .getElementById('gemini_auth_key_container')
635
+ .classList.add('hidden');
636
+ } else if (constants.LLMModel == 'gemini') {
637
+ document
638
+ .getElementById('openai_auth_key_container')
639
+ .classList.add('hidden');
640
+ document
641
+ .getElementById('gemini_auth_key_container')
642
+ .classList.remove('hidden');
643
+ }
589
644
  // skill level other
590
645
  if (constants.skillLevel == 'other') {
591
646
  document
@@ -599,7 +654,6 @@ class Menu {
599
654
  */
600
655
  SaveData() {
601
656
  constants.vol = document.getElementById('vol').value;
602
- //constants.showRect = document.getElementById('show_rect').checked;
603
657
  constants.autoPlayRate = document.getElementById('autoplay_rate').value;
604
658
  constants.brailleDisplayLength = document.getElementById(
605
659
  'braille_display_length'
@@ -609,10 +663,12 @@ class Menu {
609
663
  constants.MAX_FREQUENCY = document.getElementById('max_freq').value;
610
664
  constants.keypressInterval =
611
665
  document.getElementById('keypress_interval').value;
612
- constants.authKey = document.getElementById('chatLLM_auth_key').value;
666
+ constants.openAIAuthKey = document.getElementById('openai_auth_key').value;
667
+ constants.geminiAuthKey = document.getElementById('gemini_auth_key').value;
613
668
  constants.skillLevel = document.getElementById('skill_level').value;
614
669
  constants.skillLevelOther =
615
670
  document.getElementById('skill_level_other').value;
671
+ constants.LLMModel = document.getElementById('LLM_model').value;
616
672
 
617
673
  // aria
618
674
  if (document.getElementById('aria_mode_assertive').checked) {
@@ -650,7 +706,6 @@ class Menu {
650
706
  SaveDataToLocalStorage() {
651
707
  let data = {};
652
708
  data.vol = constants.vol;
653
- //data.showRect = constants.showRect;
654
709
  data.autoPlayRate = constants.autoPlayRate;
655
710
  data.brailleDisplayLength = constants.brailleDisplayLength;
656
711
  data.colorSelected = constants.colorSelected;
@@ -658,9 +713,11 @@ class Menu {
658
713
  data.MAX_FREQUENCY = constants.MAX_FREQUENCY;
659
714
  data.keypressInterval = constants.keypressInterval;
660
715
  data.ariaMode = constants.ariaMode;
661
- data.authKey = constants.authKey;
716
+ data.openAIAuthKey = constants.openAIAuthKey;
717
+ data.geminiAuthKey = constants.geminiAuthKey;
662
718
  data.skillLevel = constants.skillLevel;
663
719
  data.skillLevelOther = constants.skillLevelOther;
720
+ data.LLMModel = constants.LLMModel;
664
721
  localStorage.setItem('settings_data', JSON.stringify(data));
665
722
  }
666
723
  /**
@@ -670,7 +727,6 @@ class Menu {
670
727
  let data = JSON.parse(localStorage.getItem('settings_data'));
671
728
  if (data) {
672
729
  constants.vol = data.vol;
673
- //constants.showRect = data.showRect;
674
730
  constants.autoPlayRate = data.autoPlayRate;
675
731
  constants.brailleDisplayLength = data.brailleDisplayLength;
676
732
  constants.colorSelected = data.colorSelected;
@@ -678,9 +734,11 @@ class Menu {
678
734
  constants.MAX_FREQUENCY = data.MAX_FREQUENCY;
679
735
  constants.keypressInterval = data.keypressInterval;
680
736
  constants.ariaMode = data.ariaMode;
681
- constants.authKey = data.authKey;
737
+ constants.openAIAuthKey = data.openAIAuthKey;
738
+ constants.geminiAuthKey = data.geminiAuthKey;
682
739
  constants.skillLevel = data.skillLevel;
683
740
  constants.skillLevelOther = data.skillLevelOther;
741
+ constants.LLMModel = data.LLMModel ? data.LLMModel : constants.LLMModel;
684
742
  }
685
743
  this.PopulateData();
686
744
  this.UpdateHtml();
@@ -709,7 +767,7 @@ class ChatLLM {
709
767
  <div class="modal-dialog" role="document" tabindex="0">
710
768
  <div class="modal-content">
711
769
  <div class="modal-header">
712
- <h4 id="chatLLM_title" class="modal-title">Ask a Question</h4>
770
+ <h2 id="chatLLM_title" class="modal-title">Ask a Question</h2>
713
771
  <button type="button" class="close" data-dismiss="modal" aria-label="Close">
714
772
  <span aria-hidden="true">&times;</span>
715
773
  </button>
@@ -769,7 +827,7 @@ class ChatLLM {
769
827
  document,
770
828
  'keyup',
771
829
  function (e) {
772
- if (e.key == '?') {
830
+ if (e.key == '?' && (e.ctrlKey || e.metaKey)) {
773
831
  chatLLM.Toggle(true);
774
832
  }
775
833
  },
@@ -824,43 +882,15 @@ class ChatLLM {
824
882
  * @returns {void}
825
883
  */
826
884
  Submit(text, img = null) {
827
- // send text to LLM
828
- let url = 'https://api.openai.com/v1/chat/completions';
829
- //let url = 'temp';
830
-
831
- let requestJson = this.GetLLMJRequestJson(text, img);
832
- console.log(requestJson);
833
-
834
- let xhr = new XMLHttpRequest();
835
-
836
885
  // start waiting sound
837
886
  if (constants.playLLMWaitingSound) {
838
887
  chatLLM.WaitingSound(true);
839
888
  }
840
889
 
841
- if (constants.LLMDebugMode == 1) {
842
- // do the below with a 5 sec delay
843
- setTimeout(function () {
844
- chatLLM.ProcessLLMResponse(chatLLM.fakeLLMResponseData());
845
- }, 5000);
846
- } else {
847
- fetch(url, {
848
- method: 'POST',
849
- headers: {
850
- 'Content-Type': 'application/json',
851
- Authorization: 'Bearer ' + constants.authKey,
852
- },
853
- body: JSON.stringify(requestJson),
854
- })
855
- .then((response) => response.json())
856
- .then((data) => {
857
- chatLLM.ProcessLLMResponse(data);
858
- })
859
- .catch((error) => {
860
- chatLLM.WaitingSound(false);
861
- console.error('Error:', error);
862
- // also todo: handle errors somehow
863
- });
890
+ if (constants.LLMModel == 'gemini') {
891
+ chatLLM.GeminiPrompt(text, img);
892
+ } else if (constants.LLMModel == 'openai') {
893
+ chatLLM.OpenAIPrompt(text, img);
864
894
  }
865
895
  }
866
896
 
@@ -910,8 +940,38 @@ class ChatLLM {
910
940
  ProcessLLMResponse(data) {
911
941
  chatLLM.WaitingSound(false);
912
942
  console.log('LLM response: ', data);
913
- let text = data.choices[0].message.content;
914
- chatLLM.DisplayChatMessage('LLM', text);
943
+ let text = '';
944
+ let LLMName = '';
945
+
946
+ if (constants.LLMModel == 'openai') {
947
+ LLMName = 'OpenAI';
948
+ text = data.choices[0].message.content;
949
+ let i = this.requestJson.messages.length;
950
+ this.requestJson.messages[i] = {};
951
+ this.requestJson.messages[i].role = 'assistant';
952
+ this.requestJson.messages[i].content = text;
953
+
954
+ if (data.error) {
955
+ chatLLM.DisplayChatMessage(LLMName, 'Error processing request.');
956
+ } else {
957
+ chatLLM.DisplayChatMessage(LLMName, text);
958
+ }
959
+ } else if (constants.LLMModel == 'gemini') {
960
+ LLMName = 'Gemini';
961
+ if (data.text()) {
962
+ text = data.text();
963
+ chatLLM.DisplayChatMessage(LLMName, text);
964
+ } else {
965
+ if (!data.error) {
966
+ data.error = 'Error processing request.';
967
+ }
968
+ }
969
+ if (data.error) {
970
+ chatLLM.DisplayChatMessage(LLMName, 'Error processing request.');
971
+ } else {
972
+ // todo: display actual response
973
+ }
974
+ }
915
975
  }
916
976
 
917
977
  /**
@@ -976,32 +1036,60 @@ class ChatLLM {
976
1036
  /**
977
1037
  * Gets running prompt info, appends the latest request, and packages it into a JSON object for the LLM.
978
1038
  * @function
979
- * @name GetLLMJRequestJson
1039
+ * @name OpenAIPrompt
980
1040
  * @memberof module:constants
981
1041
  * @returns {json}
982
1042
  */
983
- GetLLMJRequestJson(text, img) {
1043
+ OpenAIPrompt(text, img) {
1044
+ // request init
1045
+ let url = 'https://api.openai.com/v1/chat/completions';
1046
+ let auth = constants.openAIAuthKey;
1047
+ let requestJson = chatLLM.OpenAIJson(text, img);
1048
+ console.log('LLM request: ', requestJson);
1049
+
1050
+ fetch(url, {
1051
+ method: 'POST',
1052
+ headers: {
1053
+ 'Content-Type': 'application/json',
1054
+ Authorization: 'Bearer ' + auth,
1055
+ },
1056
+ body: JSON.stringify(requestJson),
1057
+ })
1058
+ .then((response) => response.json())
1059
+ .then((data) => {
1060
+ chatLLM.ProcessLLMResponse(data);
1061
+ })
1062
+ .catch((error) => {
1063
+ chatLLM.WaitingSound(false);
1064
+ console.error('Error:', error);
1065
+ chatLLM.DisplayChatMessage('LLM', 'Error processing request.');
1066
+ // also todo: handle errors somehow
1067
+ });
1068
+ }
1069
+ OpenAIJson(text, img) {
1070
+ let sysMessage = constants.LLMSystemMessage;
1071
+ let backupMessage =
1072
+ 'Describe ' + singleMaidr.type + ' charts to a blind person';
1073
+ // headers and sys message
984
1074
  if (!this.requestJson) {
985
1075
  this.requestJson = {};
986
1076
  this.requestJson.model = 'gpt-4-vision-preview';
987
1077
  this.requestJson.max_tokens = constants.LLMmaxResponseTokens; // note: if this is too short (tested with less than 200), the response gets cut off
988
- //this.requestJson.detail = constants.LLMDetail;
1078
+
1079
+ // sys message
989
1080
  this.requestJson.messages = [];
990
1081
  this.requestJson.messages[0] = {};
991
1082
  this.requestJson.messages[0].role = 'system';
992
- this.requestJson.messages[0].content =
993
- 'You are a helpful assistant describing the chart to a blind person';
1083
+ this.requestJson.messages[0].content = sysMessage;
994
1084
  }
995
1085
 
1086
+ // user message
1087
+ // if we have an image (first time only), send the image and the text, otherwise just the text
996
1088
  let i = this.requestJson.messages.length;
997
1089
  this.requestJson.messages[i] = {};
998
1090
  this.requestJson.messages[i].role = 'user';
999
- if (constants.LLMDebugMode == 2) {
1000
- // test message only, no image
1001
- this.requestJson.messages[i].content =
1002
- 'Describe bar charts to a blind person';
1003
- } else if (img) {
1004
- let image_url = img;
1091
+ if (img) {
1092
+ // first message, include the img
1005
1093
  this.requestJson.messages[i].content = [
1006
1094
  {
1007
1095
  type: 'text',
@@ -1009,7 +1097,7 @@ class ChatLLM {
1009
1097
  },
1010
1098
  {
1011
1099
  type: 'image_url',
1012
- image_url: { url: image_url },
1100
+ image_url: { url: img },
1013
1101
  },
1014
1102
  ];
1015
1103
  } else {
@@ -1020,6 +1108,47 @@ class ChatLLM {
1020
1108
  return this.requestJson;
1021
1109
  }
1022
1110
 
1111
+ // Assuming this function is part of your existing JavaScript file
1112
+ async GeminiPrompt(text, imgBase64 = null) {
1113
+ try {
1114
+ // Save the image for next time
1115
+ if (imgBase64 == null) {
1116
+ imgBase64 = constants.LLMImage;
1117
+ } else {
1118
+ constants.LLMImage = imgBase64;
1119
+ }
1120
+ constants.LLMImage = imgBase64;
1121
+
1122
+ // Import the module
1123
+ const { GoogleGenerativeAI } = await import(
1124
+ 'https://esm.run/@google/generative-ai'
1125
+ );
1126
+ const API_KEY = constants.geminiAuthKey;
1127
+ const genAI = new GoogleGenerativeAI(API_KEY);
1128
+ const model = genAI.getGenerativeModel({ model: 'gemini-pro-vision' });
1129
+
1130
+ // Create the prompt
1131
+ const prompt = constants.LLMSystemMessage + '\n\n' + text; // Use the text parameter as the prompt
1132
+ const image = {
1133
+ inlineData: {
1134
+ data: imgBase64, // Use the base64 image string
1135
+ mimeType: 'image/png', // Or the appropriate mime type of your image
1136
+ },
1137
+ };
1138
+
1139
+ // Generate the content
1140
+ console.log('LLM request: ', prompt, image);
1141
+ const result = await model.generateContent([prompt, image]);
1142
+ console.log(result.response.text());
1143
+
1144
+ // Process the response
1145
+ chatLLM.ProcessLLMResponse(result.response);
1146
+ } catch (error) {
1147
+ console.error('Error in GeminiPrompt:', error);
1148
+ throw error; // Rethrow the error for further handling if necessary
1149
+ }
1150
+ }
1151
+
1023
1152
  /**
1024
1153
  * Displays chat message from the user and LLM in a chat history window
1025
1154
  * @function
@@ -1032,7 +1161,7 @@ class ChatLLM {
1032
1161
  <div class="chatLLM_message ${
1033
1162
  user == 'User' ? 'chatLLM_message_self' : 'chatLLM_message_other'
1034
1163
  }">
1035
- <p class="chatLLM_message_user">${user}</p>
1164
+ <h3 class="chatLLM_message_user">${user}</h3>
1036
1165
  <p class="chatLLM_message_text">${text}</p>
1037
1166
  </div>
1038
1167
  `;
@@ -1089,8 +1218,9 @@ class ChatLLM {
1089
1218
 
1090
1219
  // first time, send default query
1091
1220
  if (this.firstTime) {
1221
+ let LLMName = constants.LLMModel == 'openai' ? 'OpenAI' : 'Gemini';
1092
1222
  this.firstTime = false;
1093
- this.DisplayChatMessage('LLM', 'Processing Chart...');
1223
+ this.DisplayChatMessage(LLMName, 'Processing Chart...');
1094
1224
  this.RunDefaultPrompt();
1095
1225
  }
1096
1226
  } else {
@@ -1109,36 +1239,30 @@ class ChatLLM {
1109
1239
  async ConvertSVGtoJPG(id) {
1110
1240
  let svgElement = document.getElementById(id);
1111
1241
  return new Promise((resolve, reject) => {
1112
- // Create a canvas
1113
1242
  var canvas = document.createElement('canvas');
1114
1243
  var ctx = canvas.getContext('2d');
1115
1244
 
1116
- // Get dimensions from the SVG element
1117
- var svgRect = svgElement.getBoundingClientRect();
1118
- canvas.width = svgRect.width;
1119
- canvas.height = svgRect.height;
1120
-
1121
- // Create an image to draw the SVG
1122
- var img = new Image();
1123
-
1124
- // Convert SVG element to a data URL
1125
1245
  var svgData = new XMLSerializer().serializeToString(svgElement);
1126
- var svgBlob = new Blob([svgData], {
1127
- type: 'image/svg+xml;charset=utf-8',
1128
- });
1129
- var url = URL.createObjectURL(svgBlob);
1130
-
1131
- img.onload = function () {
1132
- // Draw the SVG on the canvas
1133
- ctx.drawImage(img, 0, 0, svgRect.width, svgRect.height);
1134
-
1135
- // Convert the canvas to JPEG
1136
- var jpegData = canvas.toDataURL('image/jpeg');
1246
+ if (!svgData.startsWith('<svg xmlns')) {
1247
+ svgData = `<svg xmlns="http://www.w3.org/2000/svg" ${svgData.slice(4)}`;
1248
+ }
1137
1249
 
1138
- // Resolve the promise with the Base64 JPEG data
1139
- resolve(jpegData);
1250
+ var svgSize =
1251
+ svgElement.viewBox.baseVal || svgElement.getBoundingClientRect();
1252
+ canvas.width = svgSize.width;
1253
+ canvas.height = svgSize.height;
1140
1254
 
1141
- // Clean up
1255
+ var img = new Image();
1256
+ img.onload = function () {
1257
+ ctx.drawImage(img, 0, 0, svgSize.width, svgSize.height);
1258
+ var jpegData = canvas.toDataURL('image/jpeg', 0.9); // 0.9 is the quality parameter
1259
+ if (constants.LLMModel == 'openai') {
1260
+ resolve(jpegData);
1261
+ } else if (constants.LLMModel == 'gemini') {
1262
+ let base64Data = jpegData.split(',')[1];
1263
+ resolve(base64Data);
1264
+ //resolve(jpegData);
1265
+ }
1142
1266
  URL.revokeObjectURL(url);
1143
1267
  };
1144
1268
 
@@ -1146,30 +1270,14 @@ class ChatLLM {
1146
1270
  reject(new Error('Error loading SVG'));
1147
1271
  };
1148
1272
 
1273
+ var svgBlob = new Blob([svgData], {
1274
+ type: 'image/svg+xml;charset=utf-8',
1275
+ });
1276
+ var url = URL.createObjectURL(svgBlob);
1149
1277
  img.src = url;
1150
1278
  });
1151
1279
  }
1152
1280
 
1153
- downloadJPEG(base64Data, filename) {
1154
- // Create a link element
1155
- var link = document.createElement('a');
1156
-
1157
- // Set the download attribute with a filename
1158
- link.download = filename;
1159
-
1160
- // Convert Base64 data to a data URL and set it as the href
1161
- link.href = base64Data;
1162
-
1163
- // Append the link to the body (required for Firefox)
1164
- document.body.appendChild(link);
1165
-
1166
- // Trigger the download
1167
- link.click();
1168
-
1169
- // Clean up
1170
- document.body.removeChild(link);
1171
- }
1172
-
1173
1281
  /**
1174
1282
  * RunDefaultPrompt is an asynchronous function that generates a prompt for describing a chart to a blind person.
1175
1283
  * It converts the chart to a JPG image using the ConvertSVGtoJPG method and then submits the prompt to the chatLLM function.
@@ -1178,7 +1286,6 @@ class ChatLLM {
1178
1286
  async RunDefaultPrompt() {
1179
1287
  //let img = await this.ConvertSVGtoImg(singleMaidr.id);
1180
1288
  let img = await this.ConvertSVGtoJPG(singleMaidr.id);
1181
- //this.downloadJPEG(img, 'test.jpg'); // test download
1182
1289
  let text = 'Describe this chart to a blind person';
1183
1290
  if (constants.skillLevel) {
1184
1291
  if (constants.skillLevel == 'other' && constants.skillLevelOther) {
@@ -1230,7 +1337,7 @@ class Description {
1230
1337
  <div class="modal-dialog" role="document" tabindex="0">
1231
1338
  <div class="modal-content">
1232
1339
  <div class="modal-header">
1233
- <h4 id="desc_title" class="modal-title">Description</h4>
1340
+ <h2 id="desc_title" class="modal-title">Description</h2>
1234
1341
  <button type="button" class="close" data-dismiss="modal" aria-label="Close">
1235
1342
  <span aria-hidden="true">&times;</span>
1236
1343
  </button>
@@ -2022,7 +2129,7 @@ class Audio {
2022
2129
  panning = 0;
2023
2130
  }
2024
2131
  } else if (constants.chartType == 'heat') {
2025
- rawFreq = plot.values[position.y][position.x];
2132
+ rawFreq = plot.data[position.y][position.x];
2026
2133
  rawPanning = position.x;
2027
2134
  frequency = this.SlideBetween(
2028
2135
  rawFreq,
@@ -2818,7 +2925,7 @@ class Display {
2818
2925
  plot.fill +
2819
2926
  ' is ';
2820
2927
  // if (constants.hasRect) {
2821
- verboseText += plot.plotData[2][position.y][position.x];
2928
+ verboseText += plot.data[position.y][position.x];
2822
2929
  // }
2823
2930
  } else {
2824
2931
  verboseText +=
@@ -2833,7 +2940,7 @@ class Display {
2833
2940
  plot.fill +
2834
2941
  ' is ';
2835
2942
  // if (constants.hasRect) {
2836
- verboseText += plot.plotData[2][position.y][position.x];
2943
+ verboseText += plot.data[position.y][position.x];
2837
2944
  // }
2838
2945
  }
2839
2946
  // terse and verbose alternate between columns and rows
@@ -2847,7 +2954,7 @@ class Display {
2847
2954
  '<p>' +
2848
2955
  plot.x_labels[position.x] +
2849
2956
  ', ' +
2850
- plot.plotData[2][position.y][position.x] +
2957
+ plot.data[position.y][position.x] +
2851
2958
  '</p>\n';
2852
2959
  } else {
2853
2960
  // row navigation
@@ -2855,7 +2962,7 @@ class Display {
2855
2962
  '<p>' +
2856
2963
  plot.y_labels[position.y] +
2857
2964
  ', ' +
2858
- plot.plotData[2][position.y][position.x] +
2965
+ plot.data[position.y][position.x] +
2859
2966
  '</p>\n';
2860
2967
  }
2861
2968
  } else if (constants.textMode == 'verbose') {
@@ -3032,11 +3139,11 @@ class Display {
3032
3139
  } else if (constants.chartType == 'line') {
3033
3140
  // line layer
3034
3141
  verboseText +=
3035
- plot.x_group_label +
3142
+ plot.plotLegend.x +
3036
3143
  ' is ' +
3037
3144
  plot.pointValuesX[position.x] +
3038
3145
  ', ' +
3039
- plot.y_group_label +
3146
+ plot.plotLegend.y +
3040
3147
  ' is ' +
3041
3148
  plot.pointValuesY[position.x];
3042
3149
 
@@ -3709,6 +3816,8 @@ class BarChart {
3709
3816
  let elements = null;
3710
3817
  if ('selector' in singleMaidr) {
3711
3818
  elements = document.querySelectorAll(singleMaidr.selector);
3819
+ } else if ('elements' in singleMaidr) {
3820
+ elements = singleMaidr.elements;
3712
3821
  }
3713
3822
 
3714
3823
  if (xlevel && data && elements) {
@@ -3745,15 +3854,6 @@ class BarChart {
3745
3854
  logError.LogAbsentElement('elements');
3746
3855
  }
3747
3856
 
3748
- // bars. The actual bar elements in the SVG. Used to highlight visually
3749
- // if ('elements' in singleMaidr) {
3750
- // this.bars = singleMaidr.elements;
3751
- // constants.hasRect = 1;
3752
- // } else {
3753
- // // this.bars = constants.chart.querySelectorAll('g[id^="geom_rect"] > rect'); // if we use plot.plotData.length instead of plot.bars.length, we don't have to include this
3754
- // constants.hasRect = 0;
3755
- // }
3756
-
3757
3857
  // column labels, both legend and tick
3758
3858
  this.columnLabels = [];
3759
3859
  let legendX = '';
@@ -3829,10 +3929,8 @@ class BarChart {
3829
3929
 
3830
3930
  if (Array.isArray(singleMaidr)) {
3831
3931
  this.plotData = singleMaidr;
3832
- } else {
3833
- if ('data' in singleMaidr) {
3834
- this.plotData = singleMaidr.data;
3835
- }
3932
+ } else if ('data' in singleMaidr) {
3933
+ this.plotData = singleMaidr.data;
3836
3934
  }
3837
3935
 
3838
3936
  // set the max and min values for the plot
@@ -4023,8 +4121,6 @@ class BoxPlot {
4023
4121
  * @constructor
4024
4122
  */
4025
4123
  constructor() {
4026
- constants.plotOrientation = 'horz'; // default
4027
-
4028
4124
  // the default sections for all boxplots
4029
4125
  this.sections = [
4030
4126
  'lower_outlier',
@@ -4036,6 +4132,8 @@ class BoxPlot {
4036
4132
  'upper_outlier',
4037
4133
  ];
4038
4134
 
4135
+ // set orientation
4136
+ constants.plotOrientation = 'horz';
4039
4137
  if ('axes' in singleMaidr) {
4040
4138
  if ('x' in singleMaidr.axes) {
4041
4139
  if ('level' in singleMaidr.axes.x) {
@@ -4116,7 +4214,11 @@ class BoxPlot {
4116
4214
 
4117
4215
  // bounds data
4118
4216
  if ('selector' in singleMaidr) {
4119
- this.plotBounds = this.GetPlotBounds();
4217
+ let elements = document.querySelector(singleMaidr.selector);
4218
+ this.plotBounds = this.GetPlotBounds(elements);
4219
+ constants.hasRect = true;
4220
+ } else if ('elements' in singleMaidr) {
4221
+ this.plotBounds = this.GetPlotBounds(singleMaidr.elements);
4120
4222
  constants.hasRect = true;
4121
4223
  } else {
4122
4224
  constants.hasRect = false;
@@ -4202,7 +4304,7 @@ class BoxPlot {
4202
4304
  * Calculates the bounding boxes for all elements in the parent element, including outliers, whiskers, and range.
4203
4305
  * @returns {Array} An array of bounding boxes for all elements.
4204
4306
  */
4205
- GetPlotBounds() {
4307
+ GetPlotBounds(elements) {
4206
4308
  // we fetch the elements in our parent,
4207
4309
  // and similar to old GetData we run through and get bounding boxes (or blanks) for everything,
4208
4310
  // and store in an identical structure
@@ -4210,7 +4312,6 @@ class BoxPlot {
4210
4312
  let plotBounds = [];
4211
4313
  let allWeNeed = this.GetAllSegmentTypes();
4212
4314
  let re = /(?:\d+(?:\.\d*)?|\.\d+)/g;
4213
- let elements = document.querySelector(singleMaidr.selector);
4214
4315
 
4215
4316
  // get initial set of elements, a parent element for all outliers, whiskers, and range
4216
4317
  let initialElemSet = [];
@@ -4781,100 +4882,36 @@ class HeatMap {
4781
4882
  }
4782
4883
  }
4783
4884
  }
4784
- let data = null;
4785
- let dataLength = 0;
4786
4885
  if ('data' in singleMaidr) {
4787
- data = singleMaidr.data;
4788
- for (let i = 0; i < data.length; i++) {
4789
- dataLength += data[i].length;
4790
- }
4886
+ this.data = singleMaidr.data;
4887
+ this.num_rows = this.data.length;
4888
+ this.num_cols = this.data[0].length;
4889
+ } else {
4890
+ // critical error, no data
4891
+ console.error('No data found in singleMaidr object');
4791
4892
  }
4792
- let elements = null;
4793
4893
  if ('selector' in singleMaidr) {
4794
- elements = document.querySelectorAll(singleMaidr.selector);
4894
+ this.elements = document.querySelectorAll(singleMaidr.selector);
4895
+ constants.hasRect = 1;
4896
+ } else if ('elements' in singleMaidr) {
4897
+ this.elements = singleMaidr.elements;
4898
+ constants.hasRect = 1;
4899
+ } else {
4900
+ this.elements = null;
4901
+ constants.hasRect = 0;
4795
4902
  }
4796
4903
 
4797
- // if (xlevel && ylevel && data && elements) {
4798
- // if (elements.length != dataLength) {
4799
- // // I didn't throw an error but give a warning
4800
- // constants.hasRect = 0;
4801
- // logError.LogDifferentLengths('data', 'elements');
4802
- // } else if (ylevel.length != data.length) {
4803
- // constants.hasRect = 0;
4804
- // logError.logDifferentLengths('y level', 'rows');
4805
- // } else if (data[0].length != xlevel.length) {
4806
- // constants.hasRect = 0;
4807
- // logError.logDifferentLengths('x level', 'columns');
4808
- // } else {
4809
- // this.plots = elements;
4810
- // constants.hasRect = 1;
4811
- // }
4812
- // } else if (ylevel && data && elements) {
4813
- // if (dataLength != elements.length) {
4814
- // constants.hasRect = 0;
4815
- // logError.logDifferentLengths('data', 'elements');
4816
- // } else if (ylevel.length != data.length) {
4817
- // constants.hasRect = 0;
4818
- // logError.logDifferentLengths('y level', 'rows');
4819
- // } else {
4820
- // this.plots = elements;
4821
- // constants.hasRect = 1;
4822
- // }
4823
- // } else if (xlevel && data && elements) {
4824
- // if (dataLength != elements.length) {
4825
- // constants.hasRect = 0;
4826
- // logError.logDifferentLengths('data', 'elements');
4827
- // } else if (xlevel.length != data[0].length) {
4828
- // constants.hasRect = 0;
4829
- // logError.logDifferentLengths('x level', 'columns');
4830
- // } else {
4831
- // this.plots = elements;
4832
- // constants.hasRect = 1;
4833
- // }
4834
- // }
4835
- // else if (xlevel && ylevel && data) {
4836
- // constants.hasRect = 0;
4837
- // if (ylevel.length != data.length) {
4838
- // logError.logDifferentLengths('y level', 'rows');
4839
- // } else if (data[0].length != xlevel.length) {
4840
- // logError.logDifferentLengths('x level', 'columns');
4841
- // }
4842
- // logError.LogAbsentElement('elements');
4843
- // }
4844
- // else if (data && elements) {
4845
- // if (dataLength != elements.length) {
4846
- // constants.hasRect = 0;
4847
- // logError.logDifferentLengths('data', 'elements');
4848
- // } else {
4849
- // this.plots = elements;
4850
- // constants.hasRect = 1;
4851
- // }
4852
- // } else if (data) {
4853
- // constants.hasRect = 0;
4854
- // if (!xlevel) logError.LogAbsentElement('x level');
4855
- // if (!ylevel) logError.LogAbsentElement('y level');
4856
- // if (!elements) logError.LogAbsentElement('elements');
4857
- // }
4858
-
4859
- this.plots = elements;
4860
- constants.hasRect = 1;
4861
-
4862
4904
  this.group_labels = this.getGroupLabels();
4863
- // this.x_labels = this.getXLabels();
4864
- // this.y_labels = this.getYLabels();
4865
4905
  this.x_labels = xlevel;
4866
4906
  this.y_labels = ylevel;
4867
4907
  this.title = this.getTitle();
4868
4908
  this.fill = this.getFill();
4869
4909
 
4870
- this.plotData = this.getHeatMapData();
4871
- this.updateConstants();
4910
+ if (constants.hasRect) {
4911
+ this.SetHeatmapRectData();
4912
+ }
4872
4913
 
4873
- this.x_coord = this.plotData[0];
4874
- this.y_coord = this.plotData[1];
4875
- this.values = this.plotData[2];
4876
- this.num_rows = this.plotData[3];
4877
- this.num_cols = this.plotData[4];
4914
+ this.updateConstants();
4878
4915
 
4879
4916
  this.x_group_label = this.group_labels[0].trim();
4880
4917
  this.y_group_label = this.group_labels[1].trim();
@@ -4885,111 +4922,77 @@ class HeatMap {
4885
4922
  * If 'data' exists in singleMaidr, it returns the norms from the data. Otherwise, it calculates the norms from the unique x and y coordinates.
4886
4923
  * @returns {Array} An array of heatmap data containing unique x and y coordinates, norms, number of rows, and number of columns.
4887
4924
  */
4888
- getHeatMapData() {
4925
+ SetHeatmapRectData() {
4926
+ // We get a set of x and y coordinates from the heatmap squares,
4927
+ // which is different and only sometimes connected to the actual data
4928
+ // note, only runs if constants.hasRect is true
4929
+
4889
4930
  // get the x_coord and y_coord to check if a square exists at the coordinates
4890
4931
  let x_coord_check = [];
4891
4932
  let y_coord_check = [];
4892
-
4893
4933
  let unique_x_coord = [];
4894
4934
  let unique_y_coord = [];
4895
- if (constants.hasRect) {
4896
- for (let i = 0; i < this.plots.length; i++) {
4897
- if (this.plots[i]) {
4898
- // heatmap SVG containing path element instead of rect
4899
- if (this.plots[i] instanceof SVGPathElement) {
4900
- // Assuming the path data is in the format "M x y L x y L x y L x y"
4901
- const path_d = this.plots[i].getAttribute('d');
4902
- const coords = path_d.match(/[\d\.]+/g).map(Number);
4903
-
4904
- const x = coords[0];
4905
- const y = coords[1];
4906
-
4907
- x_coord_check.push(parseFloat(x));
4908
- y_coord_check.push(parseFloat(y));
4909
- } else {
4910
- x_coord_check.push(parseFloat(this.plots[i].getAttribute('x')));
4911
- y_coord_check.push(parseFloat(this.plots[i].getAttribute('y')));
4912
- }
4935
+ for (let i = 0; i < this.elements.length; i++) {
4936
+ if (this.elements[i]) {
4937
+ // heatmap SVG containing path element instead of rect
4938
+ if (this.elements[i] instanceof SVGPathElement) {
4939
+ // Assuming the path data is in the format "M x y L x y L x y L x y"
4940
+ const path_d = this.elements[i].getAttribute('d');
4941
+ const regex = /[ML]\s*(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)/g;
4942
+ const match = regex.exec(path_d);
4943
+
4944
+ const coords = [Number(match[1]), Number(match[3])];
4945
+ const x = coords[0];
4946
+ const y = coords[1];
4947
+
4948
+ x_coord_check.push(parseFloat(x));
4949
+ y_coord_check.push(parseFloat(y));
4950
+ } else {
4951
+ x_coord_check.push(parseFloat(this.elements[i].getAttribute('x')));
4952
+ y_coord_check.push(parseFloat(this.elements[i].getAttribute('y')));
4913
4953
  }
4914
4954
  }
4915
-
4916
- // sort the squares to access from left to right, up to down
4917
- x_coord_check.sort(function (a, b) {
4918
- return a - b;
4919
- }); // ascending
4920
- y_coord_check.sort(function (a, b) {
4921
- return a - b;
4922
- });
4923
-
4924
- let svgScaler = this.GetSVGScaler();
4925
- // inverse scale if svg is scaled
4926
- if (svgScaler[0] == -1) {
4927
- x_coord_check = x_coord_check.reverse();
4928
- }
4929
- if (svgScaler[1] == -1) {
4930
- y_coord_check = y_coord_check.reverse();
4931
- }
4932
-
4933
- // get unique elements from x_coord and y_coord
4934
- unique_x_coord = [...new Set(x_coord_check)];
4935
- unique_y_coord = [...new Set(y_coord_check)];
4936
- }
4937
-
4938
- // get num of rows, num of cols, and total numbers of squares
4939
- let num_rows = 0;
4940
- let num_cols = 0;
4941
- let num_squares = 0;
4942
- if ('data' in singleMaidr) {
4943
- num_rows = singleMaidr.data.length;
4944
- num_cols = singleMaidr.data[0].length;
4945
- } else {
4946
- num_rows = unique_y_coord.length;
4947
- num_cols = unique_x_coord.length;
4948
4955
  }
4949
- num_squares = num_rows * num_cols;
4950
4956
 
4951
- let norms = [];
4952
- if ('data' in singleMaidr) {
4953
- norms = [...singleMaidr.data];
4954
- } else {
4955
- norms = Array(num_rows)
4956
- .fill()
4957
- .map(() => Array(num_cols).fill(0));
4958
- let min_norm = 3 * Math.pow(255, 2);
4959
- let max_norm = 0;
4960
-
4961
- for (var i = 0; i < this.plots.length; i++) {
4962
- var x_index = unique_x_coord.indexOf(x_coord_check[i]);
4963
- var y_index = unique_y_coord.indexOf(y_coord_check[i]);
4964
- let norm = this.getRGBNorm(i);
4965
- norms[y_index][x_index] = norm;
4957
+ // sort the squares to access from left to right, up to down
4958
+ x_coord_check.sort(function (a, b) {
4959
+ return a - b;
4960
+ }); // ascending
4961
+ y_coord_check.sort(function (a, b) {
4962
+ return a - b;
4963
+ });
4966
4964
 
4967
- if (norm < min_norm) min_norm = norm;
4968
- if (norm > max_norm) max_norm = norm;
4969
- }
4965
+ let svgScaler = this.GetSVGScaler();
4966
+ // inverse scale if svg has a negative scale in the actual svg
4967
+ if (svgScaler[0] == -1) {
4968
+ x_coord_check = x_coord_check.reverse();
4969
+ }
4970
+ if (svgScaler[1] == -1) {
4971
+ y_coord_check = y_coord_check.reverse();
4970
4972
  }
4971
4973
 
4972
- let plotData = [unique_x_coord, unique_y_coord, norms, num_rows, num_cols];
4974
+ // get unique elements from x_coord and y_coord
4975
+ unique_x_coord = [...new Set(x_coord_check)];
4976
+ unique_y_coord = [...new Set(y_coord_check)];
4973
4977
 
4974
- return plotData;
4978
+ this.x_coord = unique_x_coord;
4979
+ this.y_coord = unique_y_coord;
4975
4980
  }
4976
4981
 
4977
4982
  /**
4978
4983
  * Updates the constants used in the heatmap.
4984
+ * minX: 0, always
4985
+ * maxX: the x length of the data array
4986
+ * minY: the minimum value of the data array
4987
+ * maxY: the maximum value of the data array
4988
+ * autoPlayRate: the rate at which the heatmap will autoplay, based on the number of columns
4989
+ *
4979
4990
  */
4980
4991
  updateConstants() {
4981
4992
  constants.minX = 0;
4982
- constants.maxX = this.plotData[4];
4983
- constants.minY = this.plotData[2][0][0]; // initial val
4984
- constants.maxY = this.plotData[2][0][0]; // initial val
4985
- for (let i = 0; i < this.plotData[2].length; i++) {
4986
- for (let j = 0; j < this.plotData[2][i].length; j++) {
4987
- if (this.plotData[2][i][j] < constants.minY)
4988
- constants.minY = this.plotData[2][i][j];
4989
- if (this.plotData[2][i][j] > constants.maxY)
4990
- constants.maxY = this.plotData[2][i][j];
4991
- }
4992
- }
4993
+ constants.maxX = this.data[0].length - 1;
4994
+ constants.minY = Math.min(...this.data.map((row) => Math.min(...row)));
4995
+ constants.maxY = Math.max(...this.data.map((row) => Math.max(...row)));
4993
4996
  constants.autoPlayRate = Math.min(
4994
4997
  Math.ceil(constants.AUTOPLAY_DURATION / (constants.maxX + 1)),
4995
4998
  constants.MAX_SPEED
@@ -5008,7 +5011,7 @@ class HeatMap {
5008
5011
  }
5009
5012
 
5010
5013
  /**
5011
- * Returns an array of the X and Y scales of the first SVG element found in the plots array.
5014
+ * Returns an array of the X and Y scales of the first SVG element found in the elements array.
5012
5015
  * @returns {Array<number>} An array containing the X and Y scales of the SVG element.
5013
5016
  */
5014
5017
  GetSVGScaler() {
@@ -5018,7 +5021,7 @@ class HeatMap {
5018
5021
 
5019
5022
  // but first, are we even in an svg that can be scaled?
5020
5023
  let isSvg = false;
5021
- let element = this.plots[0]; // a random start, may as well be the first
5024
+ let element = this.elements[0]; // a random start, may as well be the first
5022
5025
  while (element) {
5023
5026
  if (element.tagName.toLowerCase() == 'body') {
5024
5027
  break;
@@ -5030,7 +5033,7 @@ class HeatMap {
5030
5033
  }
5031
5034
 
5032
5035
  if (isSvg) {
5033
- let element = this.plots[0]; // a random start, may as well be the first
5036
+ let element = this.elements[0]; // a random start, may as well be the first
5034
5037
  while (element) {
5035
5038
  if (element.tagName.toLowerCase() == 'body') {
5036
5039
  break;
@@ -5062,7 +5065,7 @@ class HeatMap {
5062
5065
  * @returns {number} The sum of squared values of the RGB color.
5063
5066
  */
5064
5067
  getRGBNorm(i) {
5065
- let rgb_string = this.plots[i].getAttribute('fill');
5068
+ let rgb_string = this.elements[i].getAttribute('fill');
5066
5069
  let rgb_array = rgb_string.slice(4, -1).split(',');
5067
5070
  // just get the sum of squared value of rgb, similar without sqrt, save computation
5068
5071
  return rgb_array
@@ -5220,10 +5223,10 @@ class HeatMapRect {
5220
5223
  this.x = plot.x_coord[position.x];
5221
5224
  this.y = plot.y_coord[position.y];
5222
5225
  // find which square we're on by searching for the x and y coordinates
5223
- for (let i = 0; i < plot.plots.length; i++) {
5226
+ for (let i = 0; i < plot.elements.length; i++) {
5224
5227
  if (
5225
- plot.plots[i].getAttribute('x') == this.x &&
5226
- plot.plots[i].getAttribute('y') == this.y
5228
+ plot.elements[i].getAttribute('x') == this.x &&
5229
+ plot.elements[i].getAttribute('y') == this.y
5227
5230
  ) {
5228
5231
  this.squareIndex = i;
5229
5232
  break;
@@ -5251,7 +5254,7 @@ class HeatMapRect {
5251
5254
  rect.setAttribute('stroke', constants.colorSelected);
5252
5255
  rect.setAttribute('stroke-width', this.rectStrokeWidth);
5253
5256
  rect.setAttribute('fill', 'none');
5254
- plot.plots[this.squareIndex].parentNode.appendChild(rect);
5257
+ plot.elements[this.squareIndex].parentNode.appendChild(rect);
5255
5258
  //constants.chart.appendChild(rect);
5256
5259
  }
5257
5260
  }
@@ -5267,87 +5270,12 @@ class ScatterPlot {
5267
5270
  */
5268
5271
  constructor() {
5269
5272
  this.prefix = this.GetPrefix();
5270
- // this.SetVisualHighlight();
5271
5273
  this.SetScatterLayer();
5272
5274
  this.SetLineLayer();
5273
5275
  this.SetAxes();
5274
5276
  this.svgScaler = this.GetSVGScaler();
5275
5277
  }
5276
5278
 
5277
- // SetVisualHighlight() {
5278
- // let point_index = this.GetElementIndex('point');
5279
- // let smooth_index = this.GetElementIndex('smooth');
5280
- // if (point_index && smooth_index && elements < 2) {
5281
- // logError.LogAbsentElement('point or/and smooth line elements');
5282
- // }
5283
- // if (point_index != -1) {
5284
- // this.CheckData(point_index);
5285
- // }
5286
-
5287
- // if (smooth_index != -1) {
5288
- // this.CheckData(smooth_index);
5289
- // }
5290
- // }
5291
-
5292
- // CheckData(i) {
5293
- // let elements = 'elements' in singleMaidr ? singleMaidr.elements : null;
5294
-
5295
- // // elements does not exist at all
5296
- // if (elements == null) {
5297
- // logError.LogAbsentElement('elements');
5298
- // if (i == 0) constants.hasRect = 0;
5299
- // if (i == 1) constants.hasSmooth = 0;
5300
- // return;
5301
- // }
5302
-
5303
- // // elements exists but is empty
5304
- // if (elements.length == 0) {
5305
- // logError.LogAbsentElement('elements');
5306
- // if (i == 0) constants.hasRect = 0;
5307
- // if (i == 1) constants.hasSmooth = 0;
5308
- // return;
5309
- // }
5310
-
5311
- // // elements exists but is not an array
5312
- // if (!Array.isArray(elements)) {
5313
- // logError.LogNotArray('elements');
5314
- // if (i == 0) constants.hasRect = 0;
5315
- // if (i == 1) constants.hasSmooth = 0;
5316
- // return;
5317
- // }
5318
-
5319
- // // elements.length is more than 2
5320
- // if (elements.length > 2) {
5321
- // logError.LogTooManyElements('elements', 2);
5322
- // }
5323
-
5324
- // if ('data' in singleMaidr) {
5325
- // if (i == 0) {
5326
- // // check point elements
5327
- // if (
5328
- // singleMaidr.data[i] == null ||
5329
- // singleMaidr.data[i].length != singleMaidr.elements[i].length
5330
- // ) {
5331
- // constants.hasRect = 0;
5332
- // logError.LogDifferentLengths('point data', 'point elements');
5333
- // }
5334
- // } else if (i == 1) {
5335
- // // check smooth line elements
5336
- // if (
5337
- // singleMaidr.data[i] == null ||
5338
- // (!Array.isArray(singleMaidr.data[i]) &&
5339
- // singleMaidr.data[i].length != this.chartLineX.length)
5340
- // ) {
5341
- // constants.hasSmooth = 0;
5342
- // logError.LogDifferentLengths(
5343
- // 'smooth line data',
5344
- // 'smooth line elements'
5345
- // );
5346
- // }
5347
- // }
5348
- // }
5349
- // }
5350
-
5351
5279
  /**
5352
5280
  * Sets the x and y group labels and title for the scatterplot based on the data in singleMaidr.
5353
5281
  */
@@ -5393,28 +5321,34 @@ class ScatterPlot {
5393
5321
  */
5394
5322
  SetScatterLayer() {
5395
5323
  // initially set as smooth layer (layer 2), if possible
5396
- let elIndex = this.GetElementIndex('point');
5324
+ let elIndex = this.GetElementIndex('point'); // check if we have it
5397
5325
  if (elIndex != -1) {
5398
- this.plotPoints = document.querySelectorAll(
5399
- singleMaidr.selector[elIndex]
5400
- );
5326
+ if ('selector' in singleMaidr) {
5327
+ this.plotPoints = document.querySelectorAll(
5328
+ singleMaidr.selector[elIndex]
5329
+ );
5330
+ } else if ('elements' in singleMaidr) {
5331
+ this.plotPoints = singleMaidr.elements[elIndex];
5332
+ }
5401
5333
  } else if (singleMaidr.type == 'point') {
5402
- this.plotPoints = document.querySelectorAll(singleMaidr.selector);
5334
+ if ('selector' in singleMaidr) {
5335
+ this.plotPoints = document.querySelectorAll(singleMaidr.selector);
5336
+ } else if ('elements' in singleMaidr) {
5337
+ this.plotPoints = singleMaidr.elements;
5338
+ }
5403
5339
  }
5404
- if (typeof this.plotPoints !== 'undefined') {
5405
- let svgPointCoords = this.GetSvgPointCoords();
5406
- let pointValues = this.GetPointValues();
5340
+ let svgPointCoords = this.GetSvgPointCoords();
5341
+ let pointValues = this.GetPointValues();
5407
5342
 
5408
- this.chartPointsX = svgPointCoords[0]; // x coordinates of points
5409
- this.chartPointsY = svgPointCoords[1]; // y coordinates of points
5343
+ this.chartPointsX = svgPointCoords[0]; // x coordinates of points
5344
+ this.chartPointsY = svgPointCoords[1]; // y coordinates of points
5410
5345
 
5411
- this.x = pointValues[0]; // actual values of x
5412
- this.y = pointValues[1]; // actual values of y
5346
+ this.x = pointValues[0]; // actual values of x
5347
+ this.y = pointValues[1]; // actual values of y
5413
5348
 
5414
- // for sound weight use
5415
- this.points_count = pointValues[2]; // number of each points
5416
- this.max_count = pointValues[3];
5417
- }
5349
+ // for sound weight use
5350
+ this.points_count = pointValues[2]; // number of each points
5351
+ this.max_count = pointValues[3];
5418
5352
  }
5419
5353
 
5420
5354
  /**
@@ -5422,28 +5356,34 @@ class ScatterPlot {
5422
5356
  */
5423
5357
  SetLineLayer() {
5424
5358
  // layer = 2, smooth layer (from singleMaidr types)
5425
- let elIndex = this.GetElementIndex('smooth');
5359
+ let elIndex = this.GetElementIndex('smooth'); // check if we have it
5426
5360
  if (elIndex != -1) {
5427
- this.plotLine = document.querySelectorAll(
5428
- singleMaidr.selector[elIndex]
5429
- )[0];
5361
+ if ('selector' in singleMaidr) {
5362
+ this.plotLine = document.querySelectorAll(
5363
+ singleMaidr.selector[elIndex]
5364
+ )[0];
5365
+ } else if ('elements' in singleMaidr) {
5366
+ this.plotLine = singleMaidr.elements[elIndex][0];
5367
+ }
5430
5368
  } else if (singleMaidr.type == 'smooth') {
5431
- this.plotLine = document.querySelectorAll(singleMaidr.selector);
5369
+ if ('selector' in singleMaidr) {
5370
+ this.plotLine = document.querySelectorAll(singleMaidr.selector);
5371
+ } else if ('elements' in singleMaidr) {
5372
+ this.plotLine = singleMaidr.elements;
5373
+ }
5432
5374
  }
5433
- if (typeof this.plotLine !== 'undefined') {
5434
- let svgLineCoords = this.GetSvgLineCoords();
5435
- let smoothCurvePoints = this.GetSmoothCurvePoints();
5375
+ let svgLineCoords = this.GetSvgLineCoords();
5376
+ let smoothCurvePoints = this.GetSmoothCurvePoints();
5436
5377
 
5437
- this.chartLineX = svgLineCoords[0]; // x coordinates of curve
5438
- this.chartLineY = svgLineCoords[1]; // y coordinates of curve
5378
+ this.chartLineX = svgLineCoords[0]; // x coordinates of curve
5379
+ this.chartLineY = svgLineCoords[1]; // y coordinates of curve
5439
5380
 
5440
- this.curveX = smoothCurvePoints[0]; // actual values of x
5441
- this.curvePoints = smoothCurvePoints[1]; // actual values of y
5381
+ this.curveX = smoothCurvePoints[0]; // actual values of x
5382
+ this.curvePoints = smoothCurvePoints[1]; // actual values of y
5442
5383
 
5443
- this.curveMinY = Math.min(...this.curvePoints);
5444
- this.curveMaxY = Math.max(...this.curvePoints);
5445
- this.gradient = this.GetGradient();
5446
- }
5384
+ this.curveMinY = Math.min(...this.curvePoints);
5385
+ this.curveMaxY = Math.max(...this.curvePoints);
5386
+ this.gradient = this.GetGradient();
5447
5387
  }
5448
5388
 
5449
5389
  /**
@@ -5453,13 +5393,33 @@ class ScatterPlot {
5453
5393
  GetSvgPointCoords() {
5454
5394
  let points = new Map();
5455
5395
 
5456
- for (let i = 0; i < this.plotPoints.length; i++) {
5457
- let x = parseFloat(this.plotPoints[i].getAttribute(this.prefix + 'x')); // .toFixed(1);
5458
- let y = parseFloat(this.plotPoints[i].getAttribute(this.prefix + 'y'));
5459
- if (!points.has(x)) {
5460
- points.set(x, new Set([y]));
5461
- } else {
5462
- points.get(x).add(y);
5396
+ if (this.plotPoints) {
5397
+ for (let i = 0; i < this.plotPoints.length; i++) {
5398
+ let x = parseFloat(this.plotPoints[i].getAttribute(this.prefix + 'x')); // .toFixed(1);
5399
+ let y = parseFloat(this.plotPoints[i].getAttribute(this.prefix + 'y'));
5400
+ if (!points.has(x)) {
5401
+ points.set(x, new Set([y]));
5402
+ } else {
5403
+ points.get(x).add(y);
5404
+ }
5405
+ }
5406
+ } else {
5407
+ // pull from data instead
5408
+ let elIndex = this.GetElementIndex('point');
5409
+ for (let i = 0; i < singleMaidr.data[elIndex].length; i++) {
5410
+ let x;
5411
+ let y;
5412
+ if ('x' in singleMaidr.data[elIndex][i]) {
5413
+ x = singleMaidr.data[elIndex][i]['x'];
5414
+ }
5415
+ if ('y' in singleMaidr.data[elIndex][i]) {
5416
+ y = singleMaidr.data[elIndex][i]['y'];
5417
+ }
5418
+ if (!points.has(x)) {
5419
+ points.set(x, new Set([y]));
5420
+ } else {
5421
+ points.get(x).add(y);
5422
+ }
5463
5423
  }
5464
5424
  }
5465
5425
 
@@ -5525,38 +5485,40 @@ class ScatterPlot {
5525
5485
 
5526
5486
  // but first, are we even in an svg that can be scaled?
5527
5487
  let isSvg = false;
5528
- let element = this.plotPoints[0]; // a random start, may as well be the first
5529
- while (element) {
5530
- if (element.tagName.toLowerCase() == 'body') {
5531
- break;
5532
- }
5533
- if (element.tagName && element.tagName.toLowerCase() === 'svg') {
5534
- isSvg = true;
5535
- }
5536
- element = element.parentNode;
5537
- }
5538
-
5539
- if (isSvg) {
5488
+ if (this.plotPoints) {
5540
5489
  let element = this.plotPoints[0]; // a random start, may as well be the first
5541
5490
  while (element) {
5542
5491
  if (element.tagName.toLowerCase() == 'body') {
5543
5492
  break;
5544
5493
  }
5545
- if (element.getAttribute('transform')) {
5546
- let transform = element.getAttribute('transform');
5547
- let match = transform.match(
5548
- /scale\((-?\d+(\.\d+)?),\s*(-?\d+(\.\d+)?)\)/
5549
- );
5550
- if (match) {
5551
- if (!isNaN(match[1])) {
5552
- scaleX *= parseFloat(match[1]);
5553
- }
5554
- if (!isNaN(match[3])) {
5555
- scaleY *= parseFloat(match[3]);
5494
+ if (element.tagName && element.tagName.toLowerCase() === 'svg') {
5495
+ isSvg = true;
5496
+ }
5497
+ element = element.parentNode;
5498
+ }
5499
+
5500
+ if (isSvg) {
5501
+ let element = this.plotPoints[0]; // a random start, may as well be the first
5502
+ while (element) {
5503
+ if (element.tagName.toLowerCase() == 'body') {
5504
+ break;
5505
+ }
5506
+ if (element.getAttribute('transform')) {
5507
+ let transform = element.getAttribute('transform');
5508
+ let match = transform.match(
5509
+ /scale\((-?\d+(\.\d+)?),\s*(-?\d+(\.\d+)?)\)/
5510
+ );
5511
+ if (match) {
5512
+ if (!isNaN(match[1])) {
5513
+ scaleX *= parseFloat(match[1]);
5514
+ }
5515
+ if (!isNaN(match[3])) {
5516
+ scaleY *= parseFloat(match[3]);
5517
+ }
5556
5518
  }
5557
5519
  }
5520
+ element = element.parentNode;
5558
5521
  }
5559
- element = element.parentNode;
5560
5522
  }
5561
5523
  }
5562
5524
 
@@ -5565,22 +5527,30 @@ class ScatterPlot {
5565
5527
 
5566
5528
  /**
5567
5529
  * Returns a prefix based on the element type.
5530
+ * This helps manipulate svg stuff, as the attribute info is slightly different depending on svg source
5568
5531
  * @returns {string} The prefix.
5569
5532
  */
5570
5533
  GetPrefix() {
5571
5534
  let pointIndex = this.GetElementIndex('point');
5572
5535
 
5573
- let element;
5536
+ let element = null;
5574
5537
  if (pointIndex != -1) {
5575
- element = document.querySelectorAll(singleMaidr.selector[pointIndex])[0];
5538
+ if ('selector' in singleMaidr) {
5539
+ element = document.querySelectorAll(
5540
+ singleMaidr.selector[pointIndex]
5541
+ )[0];
5542
+ } else if ('elements' in singleMaidr) {
5543
+ element = singleMaidr.elements[pointIndex][0];
5544
+ }
5576
5545
  } else if (singleMaidr.type == 'point') {
5577
- element = document.querySelectorAll(singleMaidr.selector)[0];
5546
+ if ('selector' in singleMaidr) {
5547
+ element = document.querySelectorAll(singleMaidr.selector)[0];
5548
+ } else if ('elements' in singleMaidr) {
5549
+ element = singleMaidr.elements[0];
5550
+ }
5578
5551
  }
5579
5552
  let prefix = '';
5580
- if (
5581
- 'selector' in singleMaidr &&
5582
- element.tagName.toLowerCase() === 'circle'
5583
- ) {
5553
+ if (element && element.tagName.toLowerCase() === 'circle') {
5584
5554
  prefix = 'c';
5585
5555
  }
5586
5556
  return prefix;
@@ -5733,20 +5703,32 @@ class ScatterPlot {
5733
5703
  * @returns {Array<Array<number>>} An array containing two arrays: the x-coordinates and y-coordinates.
5734
5704
  */
5735
5705
  GetSvgLineCoords() {
5736
- // extract all the y coordinates from the point attribute of polyline
5737
- let str = this.plotLine.getAttribute('points');
5738
- let coords = str.split(' ');
5739
-
5740
- let X = [];
5741
- let Y = [];
5706
+ let x_points = [];
5707
+ let y_points = [];
5742
5708
 
5743
- for (let i = 0; i < coords.length; i++) {
5744
- let coord = coords[i].split(',');
5745
- X.push(parseFloat(coord[0]));
5746
- Y.push(parseFloat(coord[1]));
5709
+ if (this.plotLine) {
5710
+ // extract all the y coordinates from the point attribute of polyline
5711
+ let str = this.plotLine.getAttribute('points');
5712
+ let coords = str.split(' ');
5713
+ for (let i = 0; i < coords.length; i++) {
5714
+ let coord = coords[i].split(',');
5715
+ x_points.push(parseFloat(coord[0]));
5716
+ y_points.push(parseFloat(coord[1]));
5717
+ }
5718
+ } else {
5719
+ // fetch from data instead
5720
+ let elIndex = this.GetElementIndex('point');
5721
+ for (let i = 0; i < singleMaidr.data[elIndex].length; i++) {
5722
+ if ('x' in singleMaidr.data[elIndex][i]) {
5723
+ x_points.push(singleMaidr.data[elIndex][i]['x']);
5724
+ }
5725
+ if ('y' in singleMaidr.data[elIndex][i]) {
5726
+ y_points.push(singleMaidr.data[elIndex][i]['y']);
5727
+ }
5728
+ }
5747
5729
  }
5748
5730
 
5749
- return [X, Y];
5731
+ return [x, y];
5750
5732
  }
5751
5733
 
5752
5734
  /**
@@ -5808,6 +5790,24 @@ class ScatterPlot {
5808
5790
 
5809
5791
  return gradients;
5810
5792
  }
5793
+
5794
+ /**
5795
+ * Returns whether or not we have elements / selectors for the given type.
5796
+ * @param {string} type - The type of element to check for. eg, 'point' or 'smooth'.
5797
+ * @returns {boolean} - True if we have elements / selectors for the given type, false otherwise.
5798
+ * @function
5799
+ * @memberof scatterplot
5800
+ */
5801
+ GetRectStatus(type) {
5802
+ let elIndex = this.GetElementIndex(type);
5803
+ if ('selector' in singleMaidr) {
5804
+ return singleMaidr.selector[elIndex] ? true : false;
5805
+ } else if ('elements' in singleMaidr) {
5806
+ return singleMaidr.elements[elIndex] ? true : false;
5807
+ } else {
5808
+ return false;
5809
+ }
5810
+ }
5811
5811
  }
5812
5812
 
5813
5813
  /**
@@ -5826,6 +5826,7 @@ class Layer0Point {
5826
5826
  this.y = plot.chartPointsY[0];
5827
5827
  this.strokeWidth = 1.35;
5828
5828
  this.circleIndex = [];
5829
+ this.hasRect = plot.GetRectStatus('point');
5829
5830
  }
5830
5831
 
5831
5832
  /**
@@ -5925,6 +5926,7 @@ class Layer1Point {
5925
5926
  this.x = plot.chartLineX[0];
5926
5927
  this.y = plot.chartLineY[0];
5927
5928
  this.strokeWidth = 1.35;
5929
+ this.hasRect = plot.GetRectStatus('point');
5928
5930
  }
5929
5931
 
5930
5932
  /**
@@ -6015,6 +6017,8 @@ class Histogram {
6015
6017
  this.bars = null;
6016
6018
  if ('selector' in singleMaidr) {
6017
6019
  this.bars = document.querySelectorAll(singleMaidr.selector);
6020
+ } else if ('elements' in singleMaidr) {
6021
+ this.bars = singleMaidr.elements;
6018
6022
  }
6019
6023
 
6020
6024
  // labels (optional)
@@ -6164,57 +6168,7 @@ class LinePlot {
6164
6168
  constructor() {
6165
6169
  this.SetLineLayer();
6166
6170
  this.SetAxes();
6167
-
6168
- let legendX = '';
6169
- let legendY = '';
6170
- if ('axes' in singleMaidr) {
6171
- // legend labels
6172
- if (singleMaidr.axes.x) {
6173
- if (singleMaidr.axes.x.label) {
6174
- if (legendX == '') {
6175
- legendX = singleMaidr.axes.x.label;
6176
- }
6177
- }
6178
- }
6179
- if (singleMaidr.axes.y) {
6180
- if (singleMaidr.axes.y.label) {
6181
- if (legendY == '') {
6182
- legendY = singleMaidr.axes.y.label;
6183
- }
6184
- }
6185
- }
6186
- }
6187
-
6188
- this.plotLegend = {
6189
- x: legendX,
6190
- y: legendY,
6191
- };
6192
-
6193
- // title
6194
- this.title = '';
6195
- if ('labels' in singleMaidr) {
6196
- if ('title' in singleMaidr.labels) {
6197
- this.title = singleMaidr.labels.title;
6198
- }
6199
- }
6200
- if (this.title == '') {
6201
- if ('title' in singleMaidr) {
6202
- this.title = singleMaidr.title;
6203
- }
6204
- }
6205
-
6206
- // subtitle
6207
- if ('labels' in singleMaidr) {
6208
- if ('subtitle' in singleMaidr.labels) {
6209
- this.subtitle = singleMaidr.labels.subtitle;
6210
- }
6211
- }
6212
- // caption
6213
- if ('labels' in singleMaidr) {
6214
- if ('caption' in singleMaidr.labels) {
6215
- this.caption = singleMaidr.labels.caption;
6216
- }
6217
- }
6171
+ this.UpdateConstants();
6218
6172
  }
6219
6173
 
6220
6174
  /**
@@ -6224,12 +6178,13 @@ class LinePlot {
6224
6178
  let elements;
6225
6179
  if ('selector' in singleMaidr) {
6226
6180
  elements = document.querySelectorAll(singleMaidr.selector);
6181
+ } else if ('elements' in singleMaidr) {
6182
+ elements = singleMaidr.elements;
6227
6183
  }
6228
6184
 
6229
- let len = elements.length;
6230
- this.plotLine = elements[len - 1];
6185
+ if (elements) {
6186
+ this.plotLine = elements[elements.length - 1];
6231
6187
 
6232
- if (typeof this.plotLine !== 'undefined') {
6233
6188
  let pointCoords = this.GetPointCoords();
6234
6189
  let pointValues = this.GetPoints();
6235
6190
 
@@ -6241,35 +6196,33 @@ class LinePlot {
6241
6196
 
6242
6197
  this.curveMinY = Math.min(...this.pointValuesY);
6243
6198
  this.curveMaxY = Math.max(...this.pointValuesY);
6244
- constants.minX = 0;
6245
- constants.maxX = this.pointValuesX.length - 1;
6246
- constants.minY = this.curveMinY;
6247
- constants.maxY = this.curveMaxY;
6248
-
6249
- constants.autoPlayRate = Math.min(
6250
- Math.ceil(constants.AUTOPLAY_DURATION / (constants.maxX + 1)),
6251
- constants.MAX_SPEED
6252
- );
6253
- constants.DEFAULT_SPEED = constants.autoPlayRate;
6254
- if (constants.autoPlayRate < constants.MIN_SPEED) {
6255
- constants.MIN_SPEED = constants.autoPlayRate;
6256
- }
6257
-
6258
- // this.gradient = this.GetGradient();
6259
6199
  }
6260
6200
  }
6261
6201
 
6262
6202
  /**
6263
- * Sets the minimum and maximum values for the x and y axes of a line plot.
6203
+ * Updates the constants for the line plot.
6204
+ * This includes the minimum and maximum x and y values, the autoplay rate, and the default speed.
6264
6205
  */
6265
- SetMinMax() {
6206
+ UpdateConstants() {
6266
6207
  constants.minX = 0;
6267
- constants.maxX = this.pointValuesX.length - 1;
6268
- constants.minY = this.curveMinY;
6269
- constants.maxY = this.curveMaxY;
6270
- constants.autoPlayRate = Math.ceil(
6271
- constants.AUTOPLAY_DURATION / (constants.maxX + 1)
6208
+ constants.maxX = singleMaidr.data.length - 1;
6209
+ constants.minY = singleMaidr.data.reduce(
6210
+ (min, item) => (item.y < min ? item.y : min),
6211
+ singleMaidr.data[0].y
6212
+ );
6213
+ constants.maxY = singleMaidr.data.reduce(
6214
+ (max, item) => (item.y > max ? item.y : max),
6215
+ singleMaidr.data[0].y
6272
6216
  );
6217
+
6218
+ constants.autoPlayRate = Math.min(
6219
+ Math.ceil(constants.AUTOPLAY_DURATION / (constants.maxX + 1)),
6220
+ constants.MAX_SPEED
6221
+ );
6222
+ constants.DEFAULT_SPEED = constants.autoPlayRate;
6223
+ if (constants.autoPlayRate < constants.MIN_SPEED) {
6224
+ constants.MIN_SPEED = constants.autoPlayRate;
6225
+ }
6273
6226
  }
6274
6227
 
6275
6228
  /**
@@ -6325,46 +6278,60 @@ class LinePlot {
6325
6278
  }
6326
6279
  }
6327
6280
 
6328
- // GetGradient() {
6329
- // let gradients = [];
6330
-
6331
- // for (let i = 0; i < this.pointValuesY.length - 1; i++) {
6332
- // let abs_grad = Math.abs(
6333
- // (this.pointValuesY[i + 1] - this.pointValuesY[i]) /
6334
- // (this.pointValuesX[i + 1] - this.pointValuesX[i])
6335
- // ).toFixed(3);
6336
- // gradients.push(abs_grad);
6337
- // }
6338
-
6339
- // gradients.push('end');
6340
-
6341
- // return gradients;
6342
- // }
6343
-
6344
6281
  /**
6345
6282
  * Sets the x and y group labels and title for the line plot based on the axes and title properties of the singleMaidr object.
6346
6283
  */
6347
6284
  SetAxes() {
6348
- this.x_group_label = '';
6349
- this.y_group_label = '';
6350
- this.title = '';
6285
+ let legendX = '';
6286
+ let legendY = '';
6351
6287
  if ('axes' in singleMaidr) {
6352
- if ('x' in singleMaidr.axes) {
6353
- if (this.x_group_label == '') {
6354
- this.x_group_label = singleMaidr.axes.x.label;
6288
+ // legend labels
6289
+ if (singleMaidr.axes.x) {
6290
+ if (singleMaidr.axes.x.label) {
6291
+ if (legendX == '') {
6292
+ legendX = singleMaidr.axes.x.label;
6293
+ }
6355
6294
  }
6356
6295
  }
6357
- if ('y' in singleMaidr.axes) {
6358
- if (this.y_group_label == '') {
6359
- this.y_group_label = singleMaidr.axes.y.label;
6296
+ if (singleMaidr.axes.y) {
6297
+ if (singleMaidr.axes.y.label) {
6298
+ if (legendY == '') {
6299
+ legendY = singleMaidr.axes.y.label;
6300
+ }
6360
6301
  }
6361
6302
  }
6362
6303
  }
6363
- if ('title' in singleMaidr) {
6364
- if (this.title == '') {
6304
+
6305
+ this.plotLegend = {
6306
+ x: legendX,
6307
+ y: legendY,
6308
+ };
6309
+
6310
+ // title
6311
+ this.title = '';
6312
+ if ('labels' in singleMaidr) {
6313
+ if ('title' in singleMaidr.labels) {
6314
+ this.title = singleMaidr.labels.title;
6315
+ }
6316
+ }
6317
+ if (this.title == '') {
6318
+ if ('title' in singleMaidr) {
6365
6319
  this.title = singleMaidr.title;
6366
6320
  }
6367
6321
  }
6322
+
6323
+ // subtitle
6324
+ if ('labels' in singleMaidr) {
6325
+ if ('subtitle' in singleMaidr.labels) {
6326
+ this.subtitle = singleMaidr.labels.subtitle;
6327
+ }
6328
+ }
6329
+ // caption
6330
+ if ('labels' in singleMaidr) {
6331
+ if ('caption' in singleMaidr.labels) {
6332
+ this.caption = singleMaidr.labels.caption;
6333
+ }
6334
+ }
6368
6335
  }
6369
6336
 
6370
6337
  /**
@@ -6455,7 +6422,6 @@ class Segmented {
6455
6422
  */
6456
6423
  constructor() {
6457
6424
  // initialize variables level, data, and elements
6458
- let level = null;
6459
6425
  let fill = null;
6460
6426
  let data = null;
6461
6427
  let elements = null;
@@ -6463,17 +6429,17 @@ class Segmented {
6463
6429
  //axes.x.level
6464
6430
  if ('x' in singleMaidr.axes) {
6465
6431
  if ('level' in singleMaidr.axes.x) {
6466
- level = singleMaidr.axes.x.level;
6432
+ this.level = singleMaidr.axes.x.level;
6467
6433
  }
6468
6434
  } else if ('y' in singleMaidr.axes) {
6469
6435
  if ('level' in singleMaidr.axes.y) {
6470
- level = singleMaidr.axes.y.level;
6436
+ this.level = singleMaidr.axes.y.level;
6471
6437
  }
6472
6438
  }
6473
6439
  // axes.fill
6474
6440
  if ('fill' in singleMaidr.axes) {
6475
6441
  if ('level' in singleMaidr.axes.fill) {
6476
- fill = singleMaidr.axes.fill.level;
6442
+ this.fill = singleMaidr.axes.fill.level;
6477
6443
  }
6478
6444
  }
6479
6445
  }
@@ -6482,6 +6448,8 @@ class Segmented {
6482
6448
  }
6483
6449
  if ('selector' in singleMaidr) {
6484
6450
  elements = document.querySelectorAll(singleMaidr.selector);
6451
+ } else if ('elements' in singleMaidr) {
6452
+ elements = singleMaidr.elements;
6485
6453
  }
6486
6454
 
6487
6455
  // gracefull failure: must have level + fill + data, elements optional
@@ -6489,9 +6457,10 @@ class Segmented {
6489
6457
  logError.LogAbsentElement('elements');
6490
6458
  constants.hasRect = 0;
6491
6459
  }
6492
- if (level != null && fill != null && data != null) {
6493
- this.level = level;
6494
- this.fill = fill.reverse(); // typically fill is in reverse order
6460
+ if (data) {
6461
+ if (this.fill) {
6462
+ this.fill = this.fill.reverse(); // typically fill is in reverse order
6463
+ }
6495
6464
  let dataAndELements = this.ParseData(data, elements);
6496
6465
  this.plotData = dataAndELements[0];
6497
6466
  this.elements = dataAndELements[1];
@@ -6579,7 +6548,12 @@ class Segmented {
6579
6548
  let plotData = [];
6580
6549
  let plotElements = [];
6581
6550
 
6582
- if (elements.length != data.length) {
6551
+ // override and kill elements if not same length as data
6552
+ if (elements) {
6553
+ if (elements.length != data.length) {
6554
+ plotElements = null;
6555
+ }
6556
+ } else {
6583
6557
  plotElements = null;
6584
6558
  }
6585
6559
 
@@ -6609,7 +6583,9 @@ class Segmented {
6609
6583
  // set actual values
6610
6584
  if (data[k].x == this.level[i] && data[k].fill == this.fill[j]) {
6611
6585
  plotData[i][j] = data[k].y;
6612
- plotElements[i][j] = elements[k];
6586
+ if (elements) {
6587
+ plotElements[i][j] = elements[k];
6588
+ }
6613
6589
  break;
6614
6590
  }
6615
6591
  }
@@ -6660,6 +6636,7 @@ class Segmented {
6660
6636
  if (constants.sonifMode == 'on') {
6661
6637
  // we play a run of tones
6662
6638
  position.z = 0;
6639
+ constants.KillSepPlay();
6663
6640
  constants.sepPlayId = setInterval(
6664
6641
  function () {
6665
6642
  // play this tone
@@ -7304,10 +7281,8 @@ class Control {
7304
7281
  window.position = new Position(-1, plot.plotData.length);
7305
7282
  }
7306
7283
  let rect;
7307
- constants.hasRect = false;
7308
- if ('selector' in singleMaidr) {
7284
+ if (constants.hasRect) {
7309
7285
  rect = new BoxplotRect();
7310
- constants.hasRect = true;
7311
7286
  }
7312
7287
  let lastPlayed = '';
7313
7288
 
@@ -8256,7 +8231,7 @@ class Control {
8256
8231
  return new Promise((resolve) => setTimeout(resolve, time));
8257
8232
  }
8258
8233
 
8259
- // helper functions
8234
+ // heat helper functions
8260
8235
  function lockPosition() {
8261
8236
  // lock to min / max postions
8262
8237
  let didLockHappen = false;
@@ -8659,7 +8634,7 @@ class Control {
8659
8634
  if (constants.showDisplay) {
8660
8635
  display.displayValues();
8661
8636
  }
8662
- if (constants.showRect) {
8637
+ if (layer0Point.hasRect) {
8663
8638
  layer0Point.UpdatePointDisplay();
8664
8639
  }
8665
8640
  if (constants.sonifMode != 'off') {
@@ -8672,9 +8647,9 @@ class Control {
8672
8647
  display.displayValues();
8673
8648
  }
8674
8649
  if (constants.showRect) {
8675
- if (constants.chartType == 'point') {
8650
+ if (constants.chartType == 'point' && layer0Point.hasRect) {
8676
8651
  layer0Point.UpdatePointDisplay();
8677
- } else {
8652
+ } else if (constants.chartType == 'smooth' && layer1Point.hasRect) {
8678
8653
  layer1Point.UpdatePointDisplay();
8679
8654
  }
8680
8655
  }
@@ -8689,7 +8664,7 @@ class Control {
8689
8664
  if (constants.showDisplayInBraille) {
8690
8665
  display.displayValues();
8691
8666
  }
8692
- if (constants.showRect) {
8667
+ if (layer1Point.hasRect) {
8693
8668
  layer1Point.UpdatePointDisplay();
8694
8669
  }
8695
8670
  if (constants.sonifMode != 'off') {