From e9ba37458f71022b71dd7eea7a7d0ae85eec01c2 Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Tue, 14 Sep 2021 15:31:26 +0200
Subject: [PATCH 01/10] Edit building to datastream mapping object

Add weather station information
---
 public/js/appChart.js | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index 2cb42c7..03757f3 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -78,6 +78,10 @@ const getDatastreamIdFromBuildingNumber = function (
       energy: { "15min": "122", "60min": "128" },
       energy_verb: { "15min": "134", "60min": "140" },
     },
+
+    weather_station_521: {
+      outside_temp: { "15min": "141", "60min": "142" },
+    },
   };
 
   if (
-- 
GitLab


From 0c2209818d54bb7a97ad1dd4f6c75d7f138d216a Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Tue, 14 Sep 2021 15:41:38 +0200
Subject: [PATCH 02/10] Edit function: draw scatter plot

Add parameters to function that hold information about the two series.

Remove hardcoded arrays used for testing.
---
 public/js/appChart.js     | 298 +++++++++++---------------------------
 public/js/dropDownList.js |   4 +-
 2 files changed, 85 insertions(+), 217 deletions(-)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index 03757f3..2c9cb0a 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -375,9 +375,9 @@ const drawHeatMapHC = function (
 /**
  * Convert the observations' phenomenonTime from an ISO 8601 string to Unix epoch
  * @param {Array} obsArray Response from SensorThings API as array
- * @returns {Array} Array of formatted observations suitable for use in a line chart
+ * @returns {Array} Array of formatted observations suitable for use in a line chart or scatter plot
  */
-const formatSTAResponseForLineChart = function (obsArray) {
+const formatSTAResponseForLineChartOrScatterPlot = function (obsArray) {
   if (!obsArray) return;
 
   const dataSTAFormatted = obsArray.map((result) => {
@@ -577,215 +577,12 @@ const observationsPromisesRLArr = datastreamsUrlRLArr.map((obsUrl) =>
 //   console.log(x)
 // );
 
-const drawScatterPlotHC = function () {
-  // Ruecklauf
-  const ruecklaufArr = [
-    [1578193200000, 69.1999969482422],
-    [1578196800000, 69.1999969482422],
-    [1578200400000, 69.1999969482422],
-    [1578204000000, 69.1999969482422],
-    [1578207600000, 69.1999969482422],
-    [1578211200000, 69.1999969482422],
-    [1578214800000, 69.1999969482422],
-    [1578218400000, 69.1999969482422],
-    [1578222000000, 69.1999969482422],
-    [1578225600000, 69.1999969482422],
-    [1578229200000, 69.1999969482422],
-    [1578232800000, 69.1999969482422],
-    [1578236400000, 69.1999969482422],
-    [1578240000000, 69.1999969482422],
-    [1578243600000, 69.1999969482422],
-    [1578247200000, 69.1999969482422],
-    [1578250800000, 69.1999969482422],
-    [1578254400000, 69.1999969482422],
-    [1578258000000, 69.1999969482422],
-    [1578261600000, 69.1999969482422],
-    [1578265200000, 69.1999969482422],
-    [1578268800000, 69.1999969482422],
-    [1578272400000, 69.1999969482422],
-    [1578276000000, 69.1999969482422],
-    [1578279600000, 69.1999969482422],
-    [1578283200000, 69.1999969482422],
-    [1578286800000, 69.1999969482422],
-    [1578290400000, 69.1999969482422],
-    [1578294000000, 69.1999969482422],
-    [1578297600000, 69.1999969482422],
-    [1578301200000, 69.1999969482422],
-    [1578304800000, 69.1999969482422],
-    [1578308400000, 69.1999969482422],
-    [1578312000000, 69.1999969482422],
-    [1578315600000, 69.1999969482422],
-    [1578319200000, 69.1999969482422],
-    [1578322800000, 69.1999969482422],
-    [1578326400000, 69.1999969482422],
-    [1578330000000, 69.1999969482422],
-    [1578333600000, 69.1999969482422],
-    [1578337200000, 69.1999969482422],
-    [1578340800000, 69.1999969482422],
-    [1578344400000, 69.1999969482422],
-    [1578348000000, 69.1999969482422],
-    [1578351600000, 70.3556109079997],
-    [1578355200000, 76.920997634146],
-    [1578358800000, 79.4292947098697],
-    [1578362400000, 79.2650916245309],
-    [1578366000000, 79.6125674172757],
-    [1578369600000, 79.0597236964905],
-    [1578373200000, 77.7484098868052],
-    [1578376800000, 77.1226613864899],
-    [1578380400000, 76.9194480415149],
-    [1578384000000, 78.0028471359237],
-    [1578387600000, 77.1535494270819],
-    [1578391200000, 75.0470741498029],
-    [1578394800000, 74.679502580818],
-    [1578398400000, 73.6077361986314],
-    [1578402000000, 72.5580677314758],
-    [1578405600000, 72.3134755830553],
-    [1578409200000, 73.2778338997311],
-    [1578412800000, 73.7656394293467],
-    [1578416400000, 74.4736907299466],
-    [1578420000000, 74.046935040758],
-    [1578423600000, 73.4957105572807],
-    [1578427200000, 73.9627712815163],
-    [1578430800000, 74.0438044241729],
-    [1578434400000, 73.3727496036106],
-    [1578438000000, 73.1666655679279],
-    [1578441600000, 73.3418058388816],
-    [1578445200000, 73.6250001589457],
-    [1578448800000, 73.829112378629],
-    [1578452400000, 74.564083528116],
-    [1578456000000, 75.5183061171072],
-    [1578459600000, 77.3372781058983],
-    [1578463200000, 78.0196371225993],
-    [1578466800000, 77.6398578971368],
-    [1578470400000, 78.5464104081542],
-    [1578474000000, 78.7977605936686],
-    [1578477600000, 76.0006624035588],
-    [1578481200000, 74.818987728345],
-    [1578484800000, 72.7776491559135],
-    [1578488400000, 71.1266380795161],
-    [1578492000000, 71.3866485616896],
-    [1578495600000, 72.1584558128357],
-    [1578499200000, 72.7288795283423],
-    [1578502800000, 73.2401491424669],
-    [1578506400000, 72.613320930343],
-    [1578510000000, 71.7903886201647],
-    [1578513600000, 71.4483344078064],
-    [1578517200000, 71.8162703686467],
-    [1578520800000, 71.3690680013303],
-    [1578524400000, 70.6132688085203],
-    [1578528000000, 69.9669739277875],
-    [1578531600000, 69.2502318650422],
-    [1578535200000, 68.8407318482576],
-    [1578538800000, 71.4223982252898],
-    [1578542400000, 68.8941290716666],
-    [1578546000000, 71.8311421724037],
-    [1578549600000, 72.5245706435945],
-  ];
-
-  // Power
-  const powerArr = [
-    [1578193200000, 0],
-    [1578196800000, 0],
-    [1578200400000, 0],
-    [1578204000000, 0],
-    [1578207600000, 0],
-    [1578211200000, 0],
-    [1578214800000, 0],
-    [1578218400000, 0],
-    [1578222000000, 0],
-    [1578225600000, 0],
-    [1578229200000, 0],
-    [1578232800000, 0],
-    [1578236400000, 0],
-    [1578240000000, 0],
-    [1578243600000, 0],
-    [1578247200000, 0],
-    [1578250800000, 0],
-    [1578254400000, 0],
-    [1578258000000, 0],
-    [1578261600000, 0],
-    [1578265200000, 0],
-    [1578268800000, 0],
-    [1578272400000, 0],
-    [1578276000000, 0],
-    [1578279600000, 0],
-    [1578283200000, 0],
-    [1578286800000, 0],
-    [1578290400000, 0],
-    [1578294000000, 0],
-    [1578297600000, 0],
-    [1578301200000, 0],
-    [1578304800000, 0],
-    [1578308400000, 0],
-    [1578312000000, 0],
-    [1578315600000, 0],
-    [1578319200000, 0],
-    [1578322800000, 0],
-    [1578326400000, 0],
-    [1578330000000, 0],
-    [1578333600000, 0],
-    [1578337200000, 0],
-    [1578340800000, 0],
-    [1578344400000, 0],
-    [1578348000000, 0],
-    [1578351600000, 0.831280025800069],
-    [1578355200000, 27.4361266860337],
-    [1578358800000, 4.02296011930285],
-    [1578362400000, 5.46578637448993],
-    [1578366000000, 189.045738115567],
-    [1578369600000, 262.879154692536],
-    [1578373200000, 182.996291840137],
-    [1578376800000, 253.720326864073],
-    [1578380400000, 266.71791350888],
-    [1578384000000, 258.650130305165],
-    [1578387600000, 256.817462126146],
-    [1578391200000, 251.198874591439],
-    [1578394800000, 245.782954276794],
-    [1578398400000, 225.835229413786],
-    [1578402000000, 191.164833256192],
-    [1578405600000, 189.317473084174],
-    [1578409200000, 160.866751228135],
-    [1578412800000, 165.104705085896],
-    [1578416400000, 185.380724406267],
-    [1578420000000, 6.318082232318],
-    [1578423600000, 22.6244981930396],
-    [1578427200000, 0.125080846609247],
-    [1578430800000, 0.858333364129066],
-    [1578434400000, 3.15562303745482],
-    [1578438000000, 1.73965485449897],
-    [1578441600000, 3.73938900530338],
-    [1578445200000, 0.641666680574417],
-    [1578448800000, 1.64397225697835],
-    [1578452400000, 14.7165156847371],
-    [1578456000000, 6.7406491904815],
-    [1578459600000, 257.018884414906],
-    [1578463200000, 282.409075120573],
-    [1578466800000, 284.999958205159],
-    [1578470400000, 291.20836768991],
-    [1578474000000, 285.753944205729],
-    [1578477600000, 248.43810322171],
-    [1578481200000, 227.135268204399],
-    [1578484800000, 182.10778157076],
-    [1578488400000, 169.076414526325],
-    [1578492000000, 160.098294117384],
-    [1578495600000, 149.832191919638],
-    [1578499200000, 195.966023142751],
-    [1578502800000, 159.01891281008],
-    [1578506400000, 3.94323859943668],
-    [1578510000000, 9.29238140483663],
-    [1578513600000, 4.09348021179692],
-    [1578517200000, 0],
-    [1578520800000, 0],
-    [1578524400000, 0],
-    [1578528000000, 0],
-    [1578531600000, 0],
-    [1578535200000, 0],
-    [1578538800000, 154.315364068476],
-    [1578542400000, 193.405831769548],
-    [1578546000000, 136.484141248209],
-    [1578549600000, 209.041194383494],
-  ];
-
+const drawScatterPlotHC = function (
+  formattedObsArrayForSeriesOne,
+  formattedDatastreamMetadataSeriesOne = "",
+  formattedObsArrayForSeriesTwo,
+  formattedDatastreamMetadataSeriesTwo = ""
+) {
   const CHART_TITLE = "Height Versus Weight of 507 Individuals by Gender";
   const CHART_SUBTITLE = "Source: Heinz  2003";
 
@@ -901,7 +698,7 @@ const drawScatterPlotHC = function () {
       {
         name: SERIES_1_NAME,
         color: SERIES_1_SYMBOL_COLOR,
-        data: ruecklaufArr,
+        data: formattedObsArrayForSeriesOne,
         tooltip: {
           valueSuffix: ` ${SERIES_1_SYMBOL}`,
         },
@@ -909,7 +706,7 @@ const drawScatterPlotHC = function () {
       {
         name: SERIES_2_NAME,
         color: SERIES_2_SYMBOL_COLOR,
-        data: powerArr,
+        data: formattedObsArrayForSeriesTwo,
         // need this property for the dual y-axes to work
         // defines the y-axis that this series refers to
         yAxis: 1,
@@ -921,7 +718,78 @@ const drawScatterPlotHC = function () {
   });
 };
 
-drawScatterPlotHC();
+(async () => {
+  const DATASTREAM_ID_SERIES_1 = getDatastreamIdFromBuildingNumber(
+    "weather_station_521",
+    "outside_temp",
+    "60min"
+  );
+  const URL_DATASTREAM_SERIES_1 = getDatastreamUrl(
+    BASE_URL,
+    DATASTREAM_ID_SERIES_1
+  );
+  const URL_OBSERVATIONS_SERIES_1 = getObservationsUrl(
+    BASE_URL,
+    DATASTREAM_ID_SERIES_1
+  );
+
+  const DATASTREAM_ID_SERIES_2 = getDatastreamIdFromBuildingNumber(
+    "225",
+    "vl",
+    "60min"
+  );
+  const URL_DATASTREAM_SERIES_2 = getDatastreamUrl(
+    BASE_URL,
+    DATASTREAM_ID_SERIES_2
+  );
+  const URL_OBSERVATIONS_SERIES_2 = getObservationsUrl(
+    BASE_URL,
+    DATASTREAM_ID_SERIES_2
+  );
+
+  // Create promises
+  const promiseDatastreamMetadataSeries1 = getDatastreamMetadata(
+    URL_DATASTREAM_SERIES_1
+  );
+  const promiseCombinedObservationsSeries1 =
+    getCombinedObservationsFromAllNextLinks(
+      axiosGetRequest(URL_OBSERVATIONS_SERIES_1, QUERY_PARAMS_COMBINED)
+    );
+
+  const promiseDatastreamMetadataSeries2 = getDatastreamMetadata(
+    URL_DATASTREAM_SERIES_2
+  );
+  const promiseCombinedObservationsSeries2 =
+    getCombinedObservationsFromAllNextLinks(
+      axiosGetRequest(URL_OBSERVATIONS_SERIES_2, QUERY_PARAMS_COMBINED)
+    );
+
+  // Pass promises to our async function
+  const metadataPlusObservationsSeries1 =
+    await getMetadataPlusObservationsForChart([
+      promiseCombinedObservationsSeries1,
+      promiseDatastreamMetadataSeries1,
+    ]);
+  const metadataPlusObservationsSeries2 =
+    await getMetadataPlusObservationsForChart([
+      promiseCombinedObservationsSeries2,
+      promiseDatastreamMetadataSeries2,
+    ]);
+
+  // Extract the metadata and the observations from resulting arrays
+  const combinedObsSeries1 = metadataPlusObservationsSeries1[0];
+  const datastreamMetadataSeries1 = metadataPlusObservationsSeries1[1];
+
+  const combinedObsSeries2 = metadataPlusObservationsSeries2[0];
+  const datastreamMetadataSeries2 = metadataPlusObservationsSeries2[1];
+
+  drawScatterPlotHC(
+    formatSTAResponseForLineChartOrScatterPlot(combinedObsSeries1),
+    formatDatastreamMetadataForChart(datastreamMetadataSeries1),
+    formatSTAResponseForLineChartOrScatterPlot(combinedObsSeries2),
+    formatDatastreamMetadataForChart(datastreamMetadataSeries2)
+  );
+})();
 
 export {
   BASE_URL,
@@ -935,7 +803,7 @@ export {
   formatDatastreamMetadataForChart,
   formatSTAResponseForHeatMap,
   drawHeatMapHC,
-  formatSTAResponseForLineChart,
+  formatSTAResponseForLineChartOrScatterPlot,
   drawLineChartHC,
   getCombinedObservationsFromAllNextLinks,
   getMetadataPlusObservationsForChart,
diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js
index 8b72d44..9b1eb8c 100644
--- a/public/js/dropDownList.js
+++ b/public/js/dropDownList.js
@@ -11,7 +11,7 @@ import {
   formatDatastreamMetadataForChart,
   formatSTAResponseForHeatMap,
   drawHeatMapHC,
-  formatSTAResponseForLineChart,
+  formatSTAResponseForLineChartOrScatterPlot,
   drawLineChartHC,
   getCombinedObservationsFromAllNextLinks,
   getMetadataPlusObservationsForChart,
@@ -306,7 +306,7 @@ const selectChartTypeFromDropDown = async function () {
 
     if (selectedChartType === "Line") {
       drawLineChartHC(
-        formatSTAResponseForLineChart(combinedObs),
+        formatSTAResponseForLineChartOrScatterPlot(combinedObs),
         formatDatastreamMetadataForChart(datastreamMetadata)
       );
     } else if (selectedChartType === "Heatmap") {
-- 
GitLab


From eba0a6dfc564936971faf144858332615461b2d1 Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Tue, 14 Sep 2021 15:53:09 +0200
Subject: [PATCH 03/10] New function: metadata from multiple datastreams

Retrieves metadata from an array of Datastream URL strings
---
 public/js/appChart.js     | 65 +++++++++++++++++++++++++++------------
 public/js/dropDownList.js |  5 +--
 2 files changed, 48 insertions(+), 22 deletions(-)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index 2c9cb0a..3dfad8e 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -167,7 +167,7 @@ const axiosGetRequest = function (urlObservations, urlParamObj) {
  * @param {String} urlDatastream A URL that fetches a Datastream from an STA instance
  * @returns {Promise} A promise that contains a metadata object for a Datastream when fulfilled
  */
-const getDatastreamMetadata = async function (urlDatastream) {
+const getMetadataFromSingleDatastream = async function (urlDatastream) {
   try {
     // Extract properties of interest
     const {
@@ -180,6 +180,29 @@ const getDatastreamMetadata = async function (urlDatastream) {
   }
 };
 
+/**
+ * Retrieve metadata from multiple datastreams
+ * @param {Array} datastreamsUrlArr An array that contains N Datastream URL strings
+ * @returns {Promise} A promise that contains an array of N Datastream metadata objects when fulfilled
+ */
+const getMetadataFromMultipleDatastreams = async function (datastreamsUrlArr) {
+  try {
+    // Array to store our final result
+    const datastreamMetadataArr = [];
+
+    // Use for/of loop - we need to maintain the order of execution of the async operations
+    for (const datastreamUrl of datastreamsUrlArr) {
+      // Metadata from a single Datastream
+      const datastreamMetadata = await getDatastreamMetadata(datastreamUrl);
+      datastreamMetadataArr.push(datastreamMetadata);
+    }
+
+    return datastreamMetadataArr;
+  } catch (err) {
+    console.error(err);
+  }
+};
+
 /**
  * Format the response containing a Datastream's metadata from Sensorthings API
  * @param {Object} datastreamMetadata An object containing a Datastream's metadata
@@ -505,20 +528,21 @@ const getCombinedObservationsFromAllNextLinks = function (
 const getMetadataPlusObservationsForChart = async function (
   metadataPlusObsPromiseArray
 ) {
-  // Array to store our final result
-  const combinedResolvedPromises = [];
+  try {
+    // Array to store our final result
+    const combinedResolvedPromises = [];
 
-  // Use for/of loop - we need to maintain the order of execution of the async operations
-  for (const promise of metadataPlusObsPromiseArray) {
-    try {
+    // Use for/of loop - we need to maintain the order of execution of the async operations
+    for (const promise of metadataPlusObsPromiseArray) {
       // Resolved value of a single promise
       const resolvedPromise = await promise;
       combinedResolvedPromises.push(resolvedPromise);
-    } catch (err) {
-      console.error(err);
     }
+
+    return combinedResolvedPromises;
+  } catch (err) {
+    console.error(err);
   }
-  return combinedResolvedPromises;
 };
 
 /**
@@ -530,20 +554,21 @@ const getMetadataPlusObservationsForChart = async function (
 const getObservationsFromMultipleDatastreams = async function (
   observationPromiseArray
 ) {
-  // Array to store our final result
-  const observationsAllDatastreamsArr = [];
+  try {
+    // Array to store our final result
+    const observationsAllDatastreamsArr = [];
 
-  // Use for/of loop - we need to maintain the order of execution of the async operations
-  for (const observationPromise of observationPromiseArray) {
-    try {
+    // Use for/of loop - we need to maintain the order of execution of the async operations
+    for (const observationPromise of observationPromiseArray) {
       // Observations from a single Datastream
       const observations = await observationPromise;
       observationsAllDatastreamsArr.push(observations);
-    } catch (err) {
-      console.error(err);
     }
+
+    return observationsAllDatastreamsArr;
+  } catch (err) {
+    console.error(err);
   }
-  return observationsAllDatastreamsArr;
 };
 
 // Building + phenomenon + sampling rate
@@ -748,7 +773,7 @@ const drawScatterPlotHC = function (
   );
 
   // Create promises
-  const promiseDatastreamMetadataSeries1 = getDatastreamMetadata(
+  const promiseDatastreamMetadataSeries1 = getMetadataFromSingleDatastream(
     URL_DATASTREAM_SERIES_1
   );
   const promiseCombinedObservationsSeries1 =
@@ -756,7 +781,7 @@ const drawScatterPlotHC = function (
       axiosGetRequest(URL_OBSERVATIONS_SERIES_1, QUERY_PARAMS_COMBINED)
     );
 
-  const promiseDatastreamMetadataSeries2 = getDatastreamMetadata(
+  const promiseDatastreamMetadataSeries2 = getMetadataFromSingleDatastream(
     URL_DATASTREAM_SERIES_2
   );
   const promiseCombinedObservationsSeries2 =
@@ -799,7 +824,7 @@ export {
   getObservationsUrl,
   createTemporalFilterString,
   axiosGetRequest,
-  getDatastreamMetadata,
+  getMetadataFromSingleDatastream,
   formatDatastreamMetadataForChart,
   formatSTAResponseForHeatMap,
   drawHeatMapHC,
diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js
index 9b1eb8c..e39dbcf 100644
--- a/public/js/dropDownList.js
+++ b/public/js/dropDownList.js
@@ -7,7 +7,7 @@ import {
   getDatastreamUrl,
   getObservationsUrl,
   axiosGetRequest,
-  getDatastreamMetadata,
+  getMetadataFromSingleDatastream,
   formatDatastreamMetadataForChart,
   formatSTAResponseForHeatMap,
   drawHeatMapHC,
@@ -289,7 +289,8 @@ const selectChartTypeFromDropDown = async function () {
     const URL_OBSERVATIONS = getObservationsUrl(BASE_URL, selectedDatastream);
 
     // Create promises
-    const promiseDatastreamMetadata = getDatastreamMetadata(URL_DATASTREAM);
+    const promiseDatastreamMetadata =
+      getMetadataFromSingleDatastream(URL_DATASTREAM);
     const promiseCombinedObservations = getCombinedObservationsFromAllNextLinks(
       axiosGetRequest(URL_OBSERVATIONS, QUERY_PARAMS_COMBINED)
     );
-- 
GitLab


From 67048cd22ca99823a1d8c341dc8f40cf62d1cff0 Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Tue, 14 Sep 2021 16:16:50 +0200
Subject: [PATCH 04/10] New function: fetch metadata and observations

... from multiple datastreams. Its single parameter is an array of
strings representing the building, sensor and sampling rate.
---
 public/js/appChart.js     | 209 ++++++++++++++++++--------------------
 public/js/dropDownList.js |  11 +-
 2 files changed, 107 insertions(+), 113 deletions(-)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index 3dfad8e..81de454 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -182,6 +182,7 @@ const getMetadataFromSingleDatastream = async function (urlDatastream) {
 
 /**
  * Retrieve metadata from multiple datastreams
+ * @async
  * @param {Array} datastreamsUrlArr An array that contains N Datastream URL strings
  * @returns {Promise} A promise that contains an array of N Datastream metadata objects when fulfilled
  */
@@ -193,7 +194,9 @@ const getMetadataFromMultipleDatastreams = async function (datastreamsUrlArr) {
     // Use for/of loop - we need to maintain the order of execution of the async operations
     for (const datastreamUrl of datastreamsUrlArr) {
       // Metadata from a single Datastream
-      const datastreamMetadata = await getDatastreamMetadata(datastreamUrl);
+      const datastreamMetadata = await getMetadataFromSingleDatastream(
+        datastreamUrl
+      );
       datastreamMetadataArr.push(datastreamMetadata);
     }
 
@@ -525,7 +528,7 @@ const getCombinedObservationsFromAllNextLinks = function (
  * @param {Promise} metadataPlusObsPromiseArray An array that contains two promises, one for datastream metadata, the other for observations
  * @returns {Promise} A promise that contains two arrays when fulfilled, one for datastream metadata and the other for observations
  */
-const getMetadataPlusObservationsForChart = async function (
+const getMetadataPlusObservationsFromSingleDatastream = async function (
   metadataPlusObsPromiseArray
 ) {
   try {
@@ -571,57 +574,93 @@ const getObservationsFromMultipleDatastreams = async function (
   }
 };
 
-// Building + phenomenon + sampling rate
-const buildingsSensorSamplingRateRLArr = [
-  ["101", "rl", "60min"],
-  ["102", "rl", "60min"],
-  ["107", "rl", "60min"],
-  ["112, 118", "rl", "60min"],
-  ["125", "rl", "60min"],
-  ["225", "rl", "60min"],
-];
-
-// Datastreams IDs
-const datastreamsRLArr = buildingsSensorSamplingRateRLArr.map((bldg) =>
-  getDatastreamIdFromBuildingNumber(...bldg)
-);
+/**
+ * Retrieve the metadata from multiple Datastreams and the Observations corresponding to these Datastreams
+ * @async
+ * @param {Array} bldgSensorSamplingRateArr A 3*N array containing buildings, sensors & sampling rates as strings, e.g. ["101", "rl", "60min"]
+ * @returns {Promise} A promise that contains a N*2 array (the first element is an array of Observations and the second element is an array of Datastream metadata objects) when fulfilled
+ */
+const getMetadataPlusObservationsFromMultipleDatastreams = async function (
+  bldgSensorSamplingRateArr
+) {
+  try {
+    if (!bldgSensorSamplingRateArr) return;
 
-// Datastreams URLs
-const datastreamsUrlRLArr = datastreamsRLArr.map((datastreamId) =>
-  getObservationsUrl(BASE_URL, datastreamId)
-);
+    // Datastreams IDs
+    const datastreamsIdsArr = bldgSensorSamplingRateArr.map(
+      (bldgSensorSamplingRate) =>
+        getDatastreamIdFromBuildingNumber(...bldgSensorSamplingRate)
+    );
 
-// Promise objects - Observations / RL
-const observationsPromisesRLArr = datastreamsUrlRLArr.map((obsUrl) =>
-  getCombinedObservationsFromAllNextLinks(
-    axiosGetRequest(obsUrl, QUERY_PARAMS_COMBINED)
-  )
-);
+    // Observations URLs
+    const observationsUrlArr = datastreamsIdsArr.map((datastreamId) =>
+      getObservationsUrl(BASE_URL, datastreamId)
+    );
+
+    // Datastreams URLs
+    const datastreamsUrlArr = datastreamsIdsArr.map((datastreamId) =>
+      getDatastreamUrl(BASE_URL, datastreamId)
+    );
+
+    // Promise objects - Observations
+    const observationsPromisesArr = observationsUrlArr.map((obsUrl) =>
+      getCombinedObservationsFromAllNextLinks(
+        axiosGetRequest(obsUrl, QUERY_PARAMS_COMBINED)
+      )
+    );
+
+    // Observations array
+    const observationsArr = await getObservationsFromMultipleDatastreams(
+      observationsPromisesArr
+    );
+
+    // Metadata array
+    const metadataArr = await getMetadataFromMultipleDatastreams(
+      datastreamsUrlArr
+    );
 
-// getObservationsFromMultipleDatastreams(observationsPromisesRLArr).then((x) =>
-//   console.log(x)
-// );
+    return [observationsArr, metadataArr];
+  } catch (err) {
+    console.error(err);
+  }
+};
 
 const drawScatterPlotHC = function (
   formattedObsArrayForSeriesOne,
-  formattedDatastreamMetadataSeriesOne = "",
+  formattedDatastreamMetadataSeriesOne,
   formattedObsArrayForSeriesTwo,
-  formattedDatastreamMetadataSeriesTwo = ""
+  formattedDatastreamMetadataSeriesTwo
 ) {
-  const CHART_TITLE = "Height Versus Weight of 507 Individuals by Gender";
-  const CHART_SUBTITLE = "Source: Heinz  2003";
+  const {
+    datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_1,
+    datastreamName: DATASTREAM_NAME_SERIES_1,
+    phenomenonName: PHENOMENON_NAME_SERIES_1,
+    unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_1,
+  } = formattedDatastreamMetadataSeriesOne;
 
-  const X_AXIS_TITLE = "Height (cm)";
+  const {
+    datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_2,
+    datastreamName: DATASTREAM_NAME_SERIES_2,
+    phenomenonName: PHENOMENON_NAME_SERIES_2,
+    unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_2,
+  } = formattedDatastreamMetadataSeriesTwo;
+
+  const CHART_TITLE = `${PHENOMENON_NAME_SERIES_1} Versus ${PHENOMENON_NAME_SERIES_2}`;
+  const CHART_SUBTITLE = `Source: ${DATASTREAM_NAME_SERIES_1} & ${DATASTREAM_NAME_SERIES_2}`;
+
+  const X_AXIS_TITLE = `Date / Time`;
 
-  const SERIES_1_NAME = "Rücklauftemp";
-  const SERIES_1_SYMBOL_COLOR = "rgba(119, 152, 191, .5)";
-  const SERIES_1_TEXT_COLOR = "rgb(119, 152, 191)"; // remove transparency from symbol color for a more "intense" color
-  const SERIES_1_SYMBOL = "°C";
+  const SERIES_1_NAME = `${PHENOMENON_NAME_SERIES_1}`;
+  const SERIES_1_SYMBOL_COLOR_RGB_ELEMENTS = "119, 152, 191";
+  const SERIES_1_SYMBOL_COLOR = `rgba(${SERIES_1_SYMBOL_COLOR_RGB_ELEMENTS}, .5)`;
+  const SERIES_1_TEXT_COLOR = `rgb(${SERIES_1_SYMBOL_COLOR_RGB_ELEMENTS})`; // remove transparency from symbol color for a more "intense" color
+  const SERIES_1_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_1}`;
 
-  const SERIES_2_NAME = "Power";
-  const SERIES_2_SYMBOL_COLOR = "rgba(223, 83, 83, .5)";
-  const SERIES_2_TEXT_COLOR = "rgb(223, 83, 83)"; // remove transparency from symbol color for a more "intense" color
-  const SERIES_2_SYMBOL = "kW";
+  const SERIES_2_NAME = `${PHENOMENON_NAME_SERIES_2}`;
+  const SERIES_2_SYMBOL_COLOR_RGB_ELEMENTS = "223, 83, 83";
+  const SERIES_2_SYMBOL_COLOR = `rgba(${SERIES_2_SYMBOL_COLOR_RGB_ELEMENTS}, .5)`;
+  const SERIES_2_TEXT_COLOR = `rgb(${SERIES_2_SYMBOL_COLOR_RGB_ELEMENTS})`; // remove transparency from symbol color for a more "intense" color
+  const SERIES_2_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_2}`;
 
   Highcharts.chart("chart-scatter-plot", {
     chart: {
@@ -744,75 +783,29 @@ const drawScatterPlotHC = function (
 };
 
 (async () => {
-  const DATASTREAM_ID_SERIES_1 = getDatastreamIdFromBuildingNumber(
-    "weather_station_521",
-    "outside_temp",
-    "60min"
-  );
-  const URL_DATASTREAM_SERIES_1 = getDatastreamUrl(
-    BASE_URL,
-    DATASTREAM_ID_SERIES_1
-  );
-  const URL_OBSERVATIONS_SERIES_1 = getObservationsUrl(
-    BASE_URL,
-    DATASTREAM_ID_SERIES_1
-  );
-
-  const DATASTREAM_ID_SERIES_2 = getDatastreamIdFromBuildingNumber(
-    "225",
-    "vl",
-    "60min"
-  );
-  const URL_DATASTREAM_SERIES_2 = getDatastreamUrl(
-    BASE_URL,
-    DATASTREAM_ID_SERIES_2
-  );
-  const URL_OBSERVATIONS_SERIES_2 = getObservationsUrl(
-    BASE_URL,
-    DATASTREAM_ID_SERIES_2
-  );
-
-  // Create promises
-  const promiseDatastreamMetadataSeries1 = getMetadataFromSingleDatastream(
-    URL_DATASTREAM_SERIES_1
-  );
-  const promiseCombinedObservationsSeries1 =
-    getCombinedObservationsFromAllNextLinks(
-      axiosGetRequest(URL_OBSERVATIONS_SERIES_1, QUERY_PARAMS_COMBINED)
-    );
-
-  const promiseDatastreamMetadataSeries2 = getMetadataFromSingleDatastream(
-    URL_DATASTREAM_SERIES_2
-  );
-  const promiseCombinedObservationsSeries2 =
-    getCombinedObservationsFromAllNextLinks(
-      axiosGetRequest(URL_OBSERVATIONS_SERIES_2, QUERY_PARAMS_COMBINED)
+  // Input array - building, sensor, samplingRate
+  const sensorsOfInterestArr = [
+    ["weather_station_521", "outside_temp", "60min"],
+    ["225", "vl", "60min"],
+  ];
+
+  const observationsPlusMetadata =
+    await getMetadataPlusObservationsFromMultipleDatastreams(
+      sensorsOfInterestArr
     );
 
-  // Pass promises to our async function
-  const metadataPlusObservationsSeries1 =
-    await getMetadataPlusObservationsForChart([
-      promiseCombinedObservationsSeries1,
-      promiseDatastreamMetadataSeries1,
-    ]);
-  const metadataPlusObservationsSeries2 =
-    await getMetadataPlusObservationsForChart([
-      promiseCombinedObservationsSeries2,
-      promiseDatastreamMetadataSeries2,
-    ]);
-
-  // Extract the metadata and the observations from resulting arrays
-  const combinedObsSeries1 = metadataPlusObservationsSeries1[0];
-  const datastreamMetadataSeries1 = metadataPlusObservationsSeries1[1];
-
-  const combinedObsSeries2 = metadataPlusObservationsSeries2[0];
-  const datastreamMetadataSeries2 = metadataPlusObservationsSeries2[1];
+  // Extract the observations and metadata for each sensor
+  // Array elements in same order as input array
+  const [
+    [obsSensorOneArr, obsSensorTwoArr],
+    [metadataSensorOne, metadataSensorTwo],
+  ] = observationsPlusMetadata;
 
   drawScatterPlotHC(
-    formatSTAResponseForLineChartOrScatterPlot(combinedObsSeries1),
-    formatDatastreamMetadataForChart(datastreamMetadataSeries1),
-    formatSTAResponseForLineChartOrScatterPlot(combinedObsSeries2),
-    formatDatastreamMetadataForChart(datastreamMetadataSeries2)
+    formatSTAResponseForLineChartOrScatterPlot(obsSensorOneArr),
+    formatDatastreamMetadataForChart(metadataSensorOne),
+    formatSTAResponseForLineChartOrScatterPlot(obsSensorTwoArr),
+    formatDatastreamMetadataForChart(metadataSensorTwo)
   );
 })();
 
@@ -831,5 +824,5 @@ export {
   formatSTAResponseForLineChartOrScatterPlot,
   drawLineChartHC,
   getCombinedObservationsFromAllNextLinks,
-  getMetadataPlusObservationsForChart,
+  getMetadataPlusObservationsFromSingleDatastream,
 };
diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js
index e39dbcf..ac3a750 100644
--- a/public/js/dropDownList.js
+++ b/public/js/dropDownList.js
@@ -14,7 +14,7 @@ import {
   formatSTAResponseForLineChartOrScatterPlot,
   drawLineChartHC,
   getCombinedObservationsFromAllNextLinks,
-  getMetadataPlusObservationsForChart,
+  getMetadataPlusObservationsFromSingleDatastream,
 } from "./appChart.js";
 
 const buildingsAvailableSensorsArr = [
@@ -296,10 +296,11 @@ const selectChartTypeFromDropDown = async function () {
     );
 
     // Pass promises to our async function
-    const metadataPlusObservations = await getMetadataPlusObservationsForChart([
-      promiseCombinedObservations,
-      promiseDatastreamMetadata,
-    ]);
+    const metadataPlusObservations =
+      await getMetadataPlusObservationsFromSingleDatastream([
+        promiseCombinedObservations,
+        promiseDatastreamMetadata,
+      ]);
 
     // Extract the metadata and the observations from resulting array
     const combinedObs = metadataPlusObservations[0];
-- 
GitLab


From aa583ae0123f30fca5eace4a41123f96491f1d85 Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Tue, 14 Sep 2021 16:25:05 +0200
Subject: [PATCH 05/10] New function: test drawing of scatter plot

Create a temporary function that wraps the scatter plot drawing logic
---
 public/js/appChart.js | 37 +++++++++++++++++++++++++++----------
 1 file changed, 27 insertions(+), 10 deletions(-)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index 81de454..fe00205 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -662,12 +662,19 @@ const drawScatterPlotHC = function (
   const SERIES_2_TEXT_COLOR = `rgb(${SERIES_2_SYMBOL_COLOR_RGB_ELEMENTS})`; // remove transparency from symbol color for a more "intense" color
   const SERIES_2_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_2}`;
 
+  const MARKER_RADIUS = 2;
+
   Highcharts.chart("chart-scatter-plot", {
     chart: {
       type: "scatter",
       zoomType: "xy",
     },
 
+    boost: {
+      useGPUTranslations: true,
+      usePreAllocated: true,
+    },
+
     title: {
       text: CHART_TITLE,
     },
@@ -721,21 +728,24 @@ const drawScatterPlotHC = function (
       },
     ],
 
+    // legend: {
+    //   layout: "vertical",
+    //   align: "left",
+    //   verticalAlign: "top",
+    //   x: 100,
+    //   y: 70,
+    //   floating: true,
+    //   backgroundColor: Highcharts.defaultOptions.chart.backgroundColor,
+    //   borderWidth: 1,
+    // },
     legend: {
-      layout: "vertical",
-      align: "left",
-      verticalAlign: "top",
-      x: 100,
-      y: 70,
-      floating: true,
-      backgroundColor: Highcharts.defaultOptions.chart.backgroundColor,
-      borderWidth: 1,
+      enabled: false,
     },
 
     plotOptions: {
       scatter: {
         marker: {
-          radius: 5,
+          radius: MARKER_RADIUS,
           states: {
             hover: {
               enabled: true,
@@ -782,7 +792,10 @@ const drawScatterPlotHC = function (
   });
 };
 
-(async () => {
+/**
+ * Test drawing of scatter plot chart
+ */
+const drawScatterPlotHCTest = async function () {
   // Input array - building, sensor, samplingRate
   const sensorsOfInterestArr = [
     ["weather_station_521", "outside_temp", "60min"],
@@ -807,6 +820,10 @@ const drawScatterPlotHC = function (
     formatSTAResponseForLineChartOrScatterPlot(obsSensorTwoArr),
     formatDatastreamMetadataForChart(metadataSensorTwo)
   );
+};
+
+(async () => {
+  await drawScatterPlotHCTest();
 })();
 
 export {
-- 
GitLab


From 60cb21f297cafc768f48515d6637cbf9f2d983fb Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Tue, 14 Sep 2021 16:34:04 +0200
Subject: [PATCH 06/10] New function: format observations for scatter plot

The function paramaters are two observation arrays which are responses
from a SensorThings API instance.

The logic of the function is based on the premise that the first input
observation array is the larger of the two.
---
 public/js/appChart.js | 42 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 41 insertions(+), 1 deletion(-)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index fe00205..e9f9dc0 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -792,6 +792,46 @@ const drawScatterPlotHC = function (
   });
 };
 
+const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
+  // Check if the arrays have the same length
+  // We want `obsArrayOne` to be the larger array
+  const [obsArrayOneChecked, obsArrayTwoChecked] = (() => {
+    if (obsArrayTwo.length > obsArrayOne.length) {
+      return [obsArrayTwo, obsArrayOne];
+    } else if (
+      obsArrayOne.length > obsArrayTwo.length ||
+      obsArrayOne.length === obsArrayTwo.length
+    ) {
+      return [obsArrayOne, obsArrayTwo];
+    }
+  })();
+
+  console.log(obsArrayOneChecked.length);
+  console.log(obsArrayTwoChecked.length);
+};
+
+(async () => {
+  const sensorsOfInterestArr = [
+    // ["225", "vl", "60min"],
+    ["125", "rl", "60min"],
+    ["weather_station_521", "outside_temp", "60min"],
+  ];
+
+  const observationsPlusMetadata =
+    await getMetadataPlusObservationsFromMultipleDatastreams(
+      sensorsOfInterestArr
+    );
+
+  // Extract the observations and metadata for each sensor
+  // Array elements in same order as input array
+  const [
+    [obsSensorOneArr, obsSensorTwoArr],
+    [metadataSensorOne, metadataSensorTwo],
+  ] = observationsPlusMetadata;
+
+  formatSTAResponseForScatterPlot(obsSensorOneArr, obsSensorTwoArr);
+})();
+
 /**
  * Test drawing of scatter plot chart
  */
@@ -823,7 +863,7 @@ const drawScatterPlotHCTest = async function () {
 };
 
 (async () => {
-  await drawScatterPlotHCTest();
+  // await drawScatterPlotHCTest();
 })();
 
 export {
-- 
GitLab


From 11981299015125d4a3ae15fef5e73c9bbd466f4b Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Tue, 14 Sep 2021 16:36:17 +0200
Subject: [PATCH 07/10] New function: get timestamp of missing observations

Currently, this function is only available in the scope of the
function that formats observations for the scatter plot chart.

Get the timestamp of the observations that are available in the first
array but are missing from the second.
---
 public/js/appChart.js | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index e9f9dc0..be10d07 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -808,6 +808,44 @@ const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
 
   console.log(obsArrayOneChecked.length);
   console.log(obsArrayTwoChecked.length);
+
+  // Create arrays with timestamps only
+  const obsArrayOneTimestamp = obsArrayOneChecked.map(
+    (obsTimeValue) => obsTimeValue[0]
+  );
+  const obsArrayTwoTimestamp = obsArrayTwoChecked.map(
+    (obsTimeValue) => obsTimeValue[0]
+  );
+
+  // console.log(obsArrayOneTimestamp);
+
+  /**
+   *
+   * @param {Array} obsTimestampArrayOne An array of timestamps for the first set of observations
+   * @param {Array} obsTimestampArrayTwo An array of timstamps for the second set of observations
+   * @returns {Array} An array of timestamps missing from the second set of observations
+   */
+  const getMissingTimestampFromSecondArray = function (
+    obsTimestampArrayOne,
+    obsTimestampArrayTwo
+  ) {
+    const differenceBetweenArrays = obsTimestampArrayOne.filter(
+      (timestampOne) => !obsTimestampArrayTwo.includes(timestampOne)
+    );
+    // .concat(
+    //   obsTimestampArrayTwo.filter(
+    //     (timestampTwo) => !obsArrayTwo.includes(timestampTwo)
+    //   )
+    // );
+
+    return differenceBetweenArrays;
+  };
+
+  const missingTimestamp = getMissingTimestampFromSecondArray(
+    obsArrayOneTimestamp,
+    obsArrayTwoTimestamp
+  );
+  console.log(missingTimestamp);
 };
 
 (async () => {
-- 
GitLab


From a9db75afb6aaefb60d9d36b8365e091fded72bd4 Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Tue, 14 Sep 2021 16:38:44 +0200
Subject: [PATCH 08/10] New function: get index of missing observations

Currently, this function is only available in the scope of the
function that formats observations for the scatter plot chart.

Get the index of the observations that are available in the first
array but are missing from the second array.
---
 public/js/appChart.js | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index be10d07..af98551 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -846,6 +846,29 @@ const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
     obsArrayTwoTimestamp
   );
   console.log(missingTimestamp);
+
+  /**
+   *
+   * @param {Array} missingTimestampsArr An array of strings representing the missing timestamps
+   * @param {Array} firstObsTimestampArr An array of timestamps for the first set of observations
+   * @returns {Array} An array of the indexes of the missing observations
+   */
+  const getIndexOfMissingObservation = function (
+    missingTimestampsArr,
+    firstObsTimestampArr
+  ) {
+    const indexesMissingObs = missingTimestampsArr.map((index) =>
+      firstObsTimestampArr.indexOf(index)
+    );
+
+    // console.log(indexes);
+    return indexesMissingObs;
+  };
+
+  const indexesMissingObsArr = getIndexOfMissingObservation(
+    missingTimestamp,
+    obsArrayOneTimestamp
+  );
 };
 
 (async () => {
-- 
GitLab


From a2cbd098976938ab86b56896b3e049a06cd745af Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Tue, 14 Sep 2021 16:40:00 +0200
Subject: [PATCH 09/10] New function: remove missing observations

Currently, this function is only available in the scope of the
function that formats observations for the scatter plot chart.

Remove the missing observations from the first (larger) array.
---
 public/js/appChart.js | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index af98551..2c925e9 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -869,6 +869,29 @@ const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
     missingTimestamp,
     obsArrayOneTimestamp
   );
+
+  /**
+   *
+   * @param {*} missingIndexesArr An array of the indexes of the observations missing from the second set of observations
+   * @param {*} obsOneArr An array of the first set of observations (timestamp + value)
+   * @returns {undefined}
+   */
+  const removeMissingObservationFromFirstArray = function (
+    missingIndexesArr,
+    obsOneArr
+  ) {
+    missingIndexesArr.forEach((index) => {
+      if (index > -1) {
+        obsOneArr.splice(index, 1);
+      }
+    });
+  };
+
+  removeMissingObservationFromFirstArray(
+    indexesMissingObsArr,
+    obsArrayOneChecked
+  );
+  console.log(obsArrayOneChecked.length);
 };
 
 (async () => {
-- 
GitLab


From 08df8dd6429db07ff98fe3c07d9b33d38a0fed18 Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Tue, 14 Sep 2021 16:55:33 +0200
Subject: [PATCH 10/10] Edit function: draw scatter plot

- Move the function within the module file

- Add documentation to the function
---
 public/js/appChart.js | 569 ++++++++++++++++++++----------------------
 1 file changed, 272 insertions(+), 297 deletions(-)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index 2c925e9..c66a8b8 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -464,6 +464,273 @@ const drawLineChartHC = function (
   });
 };
 
+/**
+ * Determines the timestamps that are missing from a smaller set of observations. Based on the comparison of two observation arrays, where one array is larger than the other
+ * @param {Array} obsTimestampArrayOne An array of timestamps for the first set of observations
+ * @param {Array} obsTimestampArrayTwo An array of timstamps for the second set of observations
+ * @returns {Array} An array of timestamps missing from either set of observations
+ */
+const getSymmetricDifferenceBetweenArrays = function (
+  obsTimestampArrayOne,
+  obsTimestampArrayTwo
+) {
+  const differenceBetweenArrays = obsTimestampArrayOne
+    .filter((timestampOne) => !obsTimestampArrayTwo.includes(timestampOne))
+    .concat(
+      obsTimestampArrayTwo.filter(
+        (timestampTwo) => !obsTimestampArrayTwo.includes(timestampTwo)
+      )
+    );
+
+  return differenceBetweenArrays;
+};
+
+/**
+ * Determines the indexes of timestamps that are missing from a smaller set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other
+ * @param {Array} missingTimestampsArr An array of strings representing the missing timestamps
+ * @param {Array} largerObsTimestampArr An array of timestamps for the larger array of observations
+ * @returns {Array} An array of the indexes of the missing observations
+ */
+const getIndexOfMissingObservation = function (
+  missingTimestampsArr,
+  largerObsTimestampArr
+) {
+  const indexesMissingObs = missingTimestampsArr.map((index) =>
+    largerObsTimestampArr.indexOf(index)
+  );
+
+  return indexesMissingObs;
+};
+
+/**
+ * Removes observations (by modifying array in place) from a larger set of observations that are missing from a smaller set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other
+ * @param {Array} missingIndexesArr An array of the indexes of the observations missing from the smaller set of observations
+ * @param {Array} largerObsArr The larger array of observations (timestamp + value) which is modified in place
+ * @returns {undefined}
+ */
+const removeMissingObservationFromLargerArray = function (
+  missingIndexesArr,
+  largerObsArr
+) {
+  missingIndexesArr.forEach((index) => {
+    if (index > -1) {
+      largerObsArr.splice(index, 1);
+    }
+  });
+};
+
+/**
+ * Compares the length of two input arrays to determine the larger one
+ * @param {Array} firstArr First input array
+ * @param {Array} secondArr Second input array
+ * @returns {Array} The larger array
+ */
+const getLargerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
+  if (firstArr.length === secondArr.length) return;
+
+  if (firstArr.length > secondArr.length) return firstArr;
+
+  if (firstArr.length < secondArr.length) return secondArr;
+};
+
+/**
+ * Extracts and combines observation values from two imput observation arrays of equal length
+ * @param {Array} obsArrayOne First set of N observations (timestamp + value)
+ * @param {Array} obsArrayTwo Second set of N observations (timestamp + value)
+ * @returns {Array} A 2*N array of observation values from both input observation arrays
+ */
+const createCombinedObservationValues = function (obsArrayOne, obsArrayTwo) {
+  // Extract the values from the two observation arrays
+  const obsValuesOne = obsArrayOne.map((result) => result[1]);
+  const obsValuesTwo = obsArrayTwo.map((result) => result[1]);
+
+  //  Since the arrays are of equal length, we need only use one of the arrays for looping
+  const obsValuesOnePlusTwo = obsValuesOne.map((obsValOne, i) => {
+    return [obsValOne, obsValuesTwo[i]];
+  });
+
+  return obsValuesOnePlusTwo;
+};
+
+/**
+ * Format the response from SensorThings API to make it suitable for scatter plot
+ * @param {Array} obsArrayOne Response from SensorThings API as array
+ * @param {Array} obsArrayTwo Response from SensorThings API as array
+ * @returns {Array} Array of formatted observations suitable for use in a scatter plot
+ */
+const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
+  // When our observation arrays have DIFFERENT lengths
+  if (obsArrayOne.length !== obsArrayTwo.length) {
+    // Create arrays with timestamps only
+    const obsArrayOneTimestamp = obsArrayOne.map(
+      (obsTimeValue) => obsTimeValue[0]
+    );
+    const obsArrayTwoTimestamp = obsArrayTwo.map(
+      (obsTimeValue) => obsTimeValue[0]
+    );
+
+    const missingTimestamp = getSymmetricDifferenceBetweenArrays(
+      obsArrayOneTimestamp,
+      obsArrayTwoTimestamp
+    );
+
+    // Determine the larger observation timestamp array
+    const biggerObsTimestampArr = getLargerArrayBetweenTwoInputArrays(
+      obsArrayOneTimestamp,
+      obsArrayTwoTimestamp
+    );
+
+    // Indexes of the missing observations
+    const indexesMissingObsArr = getIndexOfMissingObservation(
+      missingTimestamp,
+      biggerObsTimestampArr
+    );
+
+    // Determine the larger observation array
+    const biggerObsArr = getLargerArrayBetweenTwoInputArrays(
+      obsArrayOne,
+      obsArrayTwo
+    );
+
+    // Remove the missing observation from the larger array of observations
+    // Modifies the array in place
+    removeMissingObservationFromLargerArray(indexesMissingObsArr, biggerObsArr);
+
+    return createCombinedObservationValues(obsArrayOne, obsArrayTwo);
+  }
+
+  // When our observation arrays already have SAME lengths
+  return createCombinedObservationValues(obsArrayOne, obsArrayTwo);
+};
+
+/**
+ * Draw a scatter plot using Highcharts library
+ * @param {*} formattedObsArrayForSeriesOnePlusSeriesTwo Response from SensorThings API formatted for use in a scatter plot
+ * @param {*} formattedDatastreamMetadataSeriesOne Object containing Datastream metadata for the first chart series
+ * @param {*} formattedDatastreamMetadataSeriesTwo Object containing Datastream metadata for the second chart series
+ * @returns {undefined}
+ */
+const drawScatterPlotHC = function (
+  formattedObsArrayForSeriesOnePlusSeriesTwo,
+  formattedDatastreamMetadataSeriesOne,
+  formattedDatastreamMetadataSeriesTwo
+) {
+  const {
+    datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_1,
+    datastreamName: DATASTREAM_NAME_SERIES_1,
+    phenomenonName: PHENOMENON_NAME_SERIES_1,
+    unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_1,
+  } = formattedDatastreamMetadataSeriesOne;
+
+  const {
+    datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_2,
+    datastreamName: DATASTREAM_NAME_SERIES_2,
+    phenomenonName: PHENOMENON_NAME_SERIES_2,
+    unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_2,
+  } = formattedDatastreamMetadataSeriesTwo;
+
+  // Order of axes
+  // Y-Axis -- Series 2
+  // X-Axis -- Series 1
+
+  const CHART_TITLE = `${PHENOMENON_NAME_SERIES_2} Versus ${PHENOMENON_NAME_SERIES_1}`;
+  const CHART_SUBTITLE = `Source: ${DATASTREAM_NAME_SERIES_2} & ${DATASTREAM_NAME_SERIES_1}`;
+
+  const SERIES_1_NAME = `${PHENOMENON_NAME_SERIES_1}`;
+  const SERIES_1_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_1}`;
+
+  const SERIES_2_NAME = `${PHENOMENON_NAME_SERIES_2}`;
+  const SERIES_2_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_2}`;
+
+  const SERIES_COMBINED_NAME = "Y, X";
+  const SERIES_COMBINED_SYMBOL_COLOR_RGB_ELEMENTS = "223, 83, 83";
+  const SERIES_COMBINED_SYMBOL_COLOR_OPACITY = ".3";
+  const SERIES_COMBINED_SYMBOL_COLOR = `rgba(${SERIES_COMBINED_SYMBOL_COLOR_RGB_ELEMENTS}, ${SERIES_COMBINED_SYMBOL_COLOR_OPACITY})`;
+
+  const MARKER_RADIUS = 2;
+
+  Highcharts.chart("chart-scatter-plot", {
+    chart: {
+      type: "scatter",
+      zoomType: "xy",
+    },
+
+    boost: {
+      useGPUTranslations: true,
+      usePreAllocated: true,
+    },
+
+    title: {
+      text: CHART_TITLE,
+    },
+
+    subtitle: {
+      text: CHART_SUBTITLE,
+    },
+
+    xAxis: {
+      labels: {
+        format: `{value}`,
+      },
+      title: {
+        enabled: true,
+        text: `${SERIES_1_NAME} [${SERIES_1_SYMBOL}]`,
+      },
+      startOnTick: true,
+      endOnTick: true,
+      showLastLabel: true,
+    },
+
+    yAxis: [
+      {
+        labels: {
+          format: `{value}`,
+        },
+        title: {
+          text: `${SERIES_2_NAME} [${SERIES_2_SYMBOL}]`,
+        },
+      },
+    ],
+
+    legend: {
+      enabled: false,
+    },
+
+    plotOptions: {
+      scatter: {
+        marker: {
+          radius: MARKER_RADIUS,
+          states: {
+            hover: {
+              enabled: true,
+              lineColor: "rgb(100,100,100)",
+            },
+          },
+        },
+        states: {
+          hover: {
+            marker: {
+              enabled: false,
+            },
+          },
+        },
+        tooltip: {
+          headerFormat: "{series.name}<br>",
+          pointFormat: `<b>{point.y:.2f} ${SERIES_1_SYMBOL}, {point.x:.2f} ${SERIES_2_SYMBOL}</b>`,
+        },
+      },
+    },
+
+    series: [
+      {
+        name: SERIES_COMBINED_NAME,
+        color: SERIES_COMBINED_SYMBOL_COLOR,
+        data: formattedObsArrayForSeriesOnePlusSeriesTwo,
+      },
+    ],
+  });
+};
+
 /**
  * Follows "@iot.nextLink" links in SensorThingsAPI's response
  * Appends new results to existing results
@@ -625,305 +892,14 @@ const getMetadataPlusObservationsFromMultipleDatastreams = async function (
   }
 };
 
-const drawScatterPlotHC = function (
-  formattedObsArrayForSeriesOne,
-  formattedDatastreamMetadataSeriesOne,
-  formattedObsArrayForSeriesTwo,
-  formattedDatastreamMetadataSeriesTwo
-) {
-  const {
-    datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_1,
-    datastreamName: DATASTREAM_NAME_SERIES_1,
-    phenomenonName: PHENOMENON_NAME_SERIES_1,
-    unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_1,
-  } = formattedDatastreamMetadataSeriesOne;
-
-  const {
-    datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_2,
-    datastreamName: DATASTREAM_NAME_SERIES_2,
-    phenomenonName: PHENOMENON_NAME_SERIES_2,
-    unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_2,
-  } = formattedDatastreamMetadataSeriesTwo;
-
-  const CHART_TITLE = `${PHENOMENON_NAME_SERIES_1} Versus ${PHENOMENON_NAME_SERIES_2}`;
-  const CHART_SUBTITLE = `Source: ${DATASTREAM_NAME_SERIES_1} & ${DATASTREAM_NAME_SERIES_2}`;
-
-  const X_AXIS_TITLE = `Date / Time`;
-
-  const SERIES_1_NAME = `${PHENOMENON_NAME_SERIES_1}`;
-  const SERIES_1_SYMBOL_COLOR_RGB_ELEMENTS = "119, 152, 191";
-  const SERIES_1_SYMBOL_COLOR = `rgba(${SERIES_1_SYMBOL_COLOR_RGB_ELEMENTS}, .5)`;
-  const SERIES_1_TEXT_COLOR = `rgb(${SERIES_1_SYMBOL_COLOR_RGB_ELEMENTS})`; // remove transparency from symbol color for a more "intense" color
-  const SERIES_1_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_1}`;
-
-  const SERIES_2_NAME = `${PHENOMENON_NAME_SERIES_2}`;
-  const SERIES_2_SYMBOL_COLOR_RGB_ELEMENTS = "223, 83, 83";
-  const SERIES_2_SYMBOL_COLOR = `rgba(${SERIES_2_SYMBOL_COLOR_RGB_ELEMENTS}, .5)`;
-  const SERIES_2_TEXT_COLOR = `rgb(${SERIES_2_SYMBOL_COLOR_RGB_ELEMENTS})`; // remove transparency from symbol color for a more "intense" color
-  const SERIES_2_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_2}`;
-
-  const MARKER_RADIUS = 2;
-
-  Highcharts.chart("chart-scatter-plot", {
-    chart: {
-      type: "scatter",
-      zoomType: "xy",
-    },
-
-    boost: {
-      useGPUTranslations: true,
-      usePreAllocated: true,
-    },
-
-    title: {
-      text: CHART_TITLE,
-    },
-
-    subtitle: {
-      text: CHART_SUBTITLE,
-    },
-
-    xAxis: {
-      title: {
-        enabled: true,
-        text: X_AXIS_TITLE,
-      },
-      type: "datetime",
-      startOnTick: true,
-      endOnTick: true,
-      showLastLabel: true,
-    },
-
-    yAxis: [
-      {
-        // Primary yAxis
-        labels: {
-          format: `{value} ${SERIES_1_SYMBOL}`,
-          style: {
-            color: SERIES_1_TEXT_COLOR,
-          },
-        },
-        title: {
-          text: SERIES_1_NAME,
-          style: {
-            color: SERIES_1_TEXT_COLOR,
-          },
-        },
-      },
-      {
-        // Secondary yAxis
-        title: {
-          text: SERIES_2_NAME,
-          style: {
-            color: SERIES_2_TEXT_COLOR,
-          },
-        },
-        labels: {
-          format: `{value} ${SERIES_2_SYMBOL}`,
-          style: {
-            color: SERIES_2_TEXT_COLOR,
-          },
-        },
-        opposite: true,
-      },
-    ],
-
-    // legend: {
-    //   layout: "vertical",
-    //   align: "left",
-    //   verticalAlign: "top",
-    //   x: 100,
-    //   y: 70,
-    //   floating: true,
-    //   backgroundColor: Highcharts.defaultOptions.chart.backgroundColor,
-    //   borderWidth: 1,
-    // },
-    legend: {
-      enabled: false,
-    },
-
-    plotOptions: {
-      scatter: {
-        marker: {
-          radius: MARKER_RADIUS,
-          states: {
-            hover: {
-              enabled: true,
-              lineColor: "rgb(100,100,100)",
-            },
-          },
-        },
-        states: {
-          hover: {
-            marker: {
-              enabled: false,
-            },
-          },
-        },
-        tooltip: {
-          headerFormat: "{point.x:%e %b, %Y %H:%M:%S}: <b>{point.y}</b>",
-          pointFormat: "{point.x} cm, {point.y} kg",
-          valueDecimals: 2,
-        },
-      },
-    },
-
-    series: [
-      {
-        name: SERIES_1_NAME,
-        color: SERIES_1_SYMBOL_COLOR,
-        data: formattedObsArrayForSeriesOne,
-        tooltip: {
-          valueSuffix: ` ${SERIES_1_SYMBOL}`,
-        },
-      },
-      {
-        name: SERIES_2_NAME,
-        color: SERIES_2_SYMBOL_COLOR,
-        data: formattedObsArrayForSeriesTwo,
-        // need this property for the dual y-axes to work
-        // defines the y-axis that this series refers to
-        yAxis: 1,
-        tooltip: {
-          valueSuffix: ` ${SERIES_2_SYMBOL}`,
-        },
-      },
-    ],
-  });
-};
-
-const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
-  // Check if the arrays have the same length
-  // We want `obsArrayOne` to be the larger array
-  const [obsArrayOneChecked, obsArrayTwoChecked] = (() => {
-    if (obsArrayTwo.length > obsArrayOne.length) {
-      return [obsArrayTwo, obsArrayOne];
-    } else if (
-      obsArrayOne.length > obsArrayTwo.length ||
-      obsArrayOne.length === obsArrayTwo.length
-    ) {
-      return [obsArrayOne, obsArrayTwo];
-    }
-  })();
-
-  console.log(obsArrayOneChecked.length);
-  console.log(obsArrayTwoChecked.length);
-
-  // Create arrays with timestamps only
-  const obsArrayOneTimestamp = obsArrayOneChecked.map(
-    (obsTimeValue) => obsTimeValue[0]
-  );
-  const obsArrayTwoTimestamp = obsArrayTwoChecked.map(
-    (obsTimeValue) => obsTimeValue[0]
-  );
-
-  // console.log(obsArrayOneTimestamp);
-
-  /**
-   *
-   * @param {Array} obsTimestampArrayOne An array of timestamps for the first set of observations
-   * @param {Array} obsTimestampArrayTwo An array of timstamps for the second set of observations
-   * @returns {Array} An array of timestamps missing from the second set of observations
-   */
-  const getMissingTimestampFromSecondArray = function (
-    obsTimestampArrayOne,
-    obsTimestampArrayTwo
-  ) {
-    const differenceBetweenArrays = obsTimestampArrayOne.filter(
-      (timestampOne) => !obsTimestampArrayTwo.includes(timestampOne)
-    );
-    // .concat(
-    //   obsTimestampArrayTwo.filter(
-    //     (timestampTwo) => !obsArrayTwo.includes(timestampTwo)
-    //   )
-    // );
-
-    return differenceBetweenArrays;
-  };
-
-  const missingTimestamp = getMissingTimestampFromSecondArray(
-    obsArrayOneTimestamp,
-    obsArrayTwoTimestamp
-  );
-  console.log(missingTimestamp);
-
-  /**
-   *
-   * @param {Array} missingTimestampsArr An array of strings representing the missing timestamps
-   * @param {Array} firstObsTimestampArr An array of timestamps for the first set of observations
-   * @returns {Array} An array of the indexes of the missing observations
-   */
-  const getIndexOfMissingObservation = function (
-    missingTimestampsArr,
-    firstObsTimestampArr
-  ) {
-    const indexesMissingObs = missingTimestampsArr.map((index) =>
-      firstObsTimestampArr.indexOf(index)
-    );
-
-    // console.log(indexes);
-    return indexesMissingObs;
-  };
-
-  const indexesMissingObsArr = getIndexOfMissingObservation(
-    missingTimestamp,
-    obsArrayOneTimestamp
-  );
-
-  /**
-   *
-   * @param {*} missingIndexesArr An array of the indexes of the observations missing from the second set of observations
-   * @param {*} obsOneArr An array of the first set of observations (timestamp + value)
-   * @returns {undefined}
-   */
-  const removeMissingObservationFromFirstArray = function (
-    missingIndexesArr,
-    obsOneArr
-  ) {
-    missingIndexesArr.forEach((index) => {
-      if (index > -1) {
-        obsOneArr.splice(index, 1);
-      }
-    });
-  };
-
-  removeMissingObservationFromFirstArray(
-    indexesMissingObsArr,
-    obsArrayOneChecked
-  );
-  console.log(obsArrayOneChecked.length);
-};
-
-(async () => {
-  const sensorsOfInterestArr = [
-    // ["225", "vl", "60min"],
-    ["125", "rl", "60min"],
-    ["weather_station_521", "outside_temp", "60min"],
-  ];
-
-  const observationsPlusMetadata =
-    await getMetadataPlusObservationsFromMultipleDatastreams(
-      sensorsOfInterestArr
-    );
-
-  // Extract the observations and metadata for each sensor
-  // Array elements in same order as input array
-  const [
-    [obsSensorOneArr, obsSensorTwoArr],
-    [metadataSensorOne, metadataSensorTwo],
-  ] = observationsPlusMetadata;
-
-  formatSTAResponseForScatterPlot(obsSensorOneArr, obsSensorTwoArr);
-})();
-
 /**
  * Test drawing of scatter plot chart
  */
-const drawScatterPlotHCTest = async function () {
-  // Input array - building, sensor, samplingRate
+const drawScatterPlotHCTest2 = async function () {
   const sensorsOfInterestArr = [
-    ["weather_station_521", "outside_temp", "60min"],
     ["225", "vl", "60min"],
+    // ["125", "rl", "60min"],
+    ["weather_station_521", "outside_temp", "60min"],
   ];
 
   const observationsPlusMetadata =
@@ -939,15 +915,14 @@ const drawScatterPlotHCTest = async function () {
   ] = observationsPlusMetadata;
 
   drawScatterPlotHC(
-    formatSTAResponseForLineChartOrScatterPlot(obsSensorOneArr),
+    formatSTAResponseForScatterPlot(obsSensorOneArr, obsSensorTwoArr),
     formatDatastreamMetadataForChart(metadataSensorOne),
-    formatSTAResponseForLineChartOrScatterPlot(obsSensorTwoArr),
     formatDatastreamMetadataForChart(metadataSensorTwo)
   );
 };
 
 (async () => {
-  // await drawScatterPlotHCTest();
+  await drawScatterPlotHCTest2();
 })();
 
 export {
-- 
GitLab