From 0c48cb3632a727e4151cbd5cb5d4724edb1bc0e2 Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Mon, 11 Oct 2021 16:16:22 +0200
Subject: [PATCH] Check selected options validity before fetch data

Confirm that the selected drop-dowwn options are valid before fetching
the observations
---
 public/js/appChart.js                         | 105 +++++++++--------
 public/js/src_modules/chartColumn.mjs         |   7 +-
 public/js/src_modules/chartHeatmap.mjs        |  14 ++-
 public/js/src_modules/chartHelpers.mjs        |  94 ++++++++++++---
 public/js/src_modules/chartLine.mjs           |   6 +-
 public/js/src_modules/chartScatterPlot.mjs    |   6 +-
 public/js/src_modules/dropDownListHelpers.mjs | 110 ++++++++++++++++--
 .../js/src_modules/dropDownListProcessing.mjs |  79 +++++--------
 8 files changed, 281 insertions(+), 140 deletions(-)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index 638884c..f78f42e 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -40,7 +40,8 @@ import {
   deleteTemperatureDifferenceOptions,
   extractTemperatureDifferenceOptions,
   extractBuildingPlusSamplingRate,
-  checkIfChartRequiresRawObservations,
+  checkIfSelectedBuildingDataPointsOptionsAreValid,
+  checkIfSelectedAggregationOptionsAreValid,
   getAbbreviationsForSelectedOptionsFromAllDropDownLists,
 } from "./src_modules/dropDownListHelpers.mjs";
 
@@ -137,9 +138,9 @@ const drawChartUsingSelectedOptions = async function () {
     const selectedOptionsAllDropDownLists =
       getSelectedOptionsFromAllDropDownLists();
 
-    // Note: The aggregation type + duration and chart type are the second and
-    // fourth elements respectively, we have ignored the first and third elements
-    const [, selectedAggregationTypeDurationArr, , selectedChartTypeArr] =
+    // Note: The aggregation type + duration and chart type are the first and
+    // third elements respectively, we have ignored the second and fourth elements
+    const [selectedChartTypeArr, , selectedAggregationTypeDurationArr] =
       selectedOptionsAllDropDownLists;
 
     // Create an array of aggregation type and duration
@@ -152,41 +153,56 @@ const drawChartUsingSelectedOptions = async function () {
     const [selectedAggregationTypeDurationSplitArr] =
       selectedAggregationTypeDurationSplitNestedArr;
 
-    const [selectedAggregationTypeArr, selectedAggregationDuration] =
+    const [selectedAggregationType, selectedAggregationDuration] =
       selectedAggregationTypeDurationSplitArr;
 
     // Array of building(s) + data point(s) + sampling rate
-    const selectedBuildingsDataPointsSamplingRateAbbrev =
+    const selectedBuildingsDataPointsSamplingRateAbbrevNestedArr =
       getAbbreviationsForSelectedOptionsFromAllDropDownLists(
         selectedOptionsAllDropDownLists
       );
 
+    // The values of these references is either equal to `true` or an error will be thrown
+    // We would like the errors to be thrown at this point before we begin performing any asynchronous tasks
+    const selectedAggregationOptionsAreValid =
+      checkIfSelectedAggregationOptionsAreValid(
+        selectedChartTypeArr,
+        selectedAggregationType,
+        selectedAggregationDuration
+      );
+
+    const selectedBuildingDataPointsOptionsAreValid =
+      checkIfSelectedBuildingDataPointsOptionsAreValid(
+        selectedChartTypeArr,
+        selectedBuildingsDataPointsSamplingRateAbbrevNestedArr
+      );
+
     // Create copies of the arrays of building(s) + data point(s) + sampling rate
-    const selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy = [
-      ...selectedBuildingsDataPointsSamplingRateAbbrev,
+    const selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopyArr = [
+      ...selectedBuildingsDataPointsSamplingRateAbbrevNestedArr,
     ];
 
-    const selectedBuildingsDataPointsSamplingRateAbbrevComputedCopy = [
-      ...selectedBuildingsDataPointsSamplingRateAbbrev,
+    const selectedBuildingsDataPointsSamplingRateAbbrevComputedCopyArr = [
+      ...selectedBuildingsDataPointsSamplingRateAbbrevNestedArr,
     ];
 
     // Check if we have non-computed
-    const selectedBuildingsDataPointsSamplingRateAbbrevNonComputed =
+    const selectedBuildingsDataPointsSamplingRateAbbrevNonComputedArr =
       checkIfSelectedOptionsContainTemperatureDifference(
-        selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy
+        selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopyArr
       )
         ? deleteTemperatureDifferenceOptions(
-            selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy
+            selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopyArr
           )
-        : selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy;
+        : selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopyArr;
 
     // Check if we have computed / dT
-    const selectedBuildingsDataPointsSamplingRateAbbrevComputed =
+    const selectedBuildingsDataPointsSamplingRateAbbrevComputedArr =
       checkIfSelectedOptionsContainTemperatureDifference(
-        selectedBuildingsDataPointsSamplingRateAbbrevComputedCopy
+        selectedBuildingsDataPointsSamplingRateAbbrevComputedCopyArr
       )
         ? extractTemperatureDifferenceOptions(
-            selectedBuildingsDataPointsSamplingRateAbbrevComputedCopy
+            selectedBuildingsDataPointsSamplingRateAbbrevComputedCopyArr
           )
         : [];
 
@@ -194,34 +210,34 @@ const drawChartUsingSelectedOptions = async function () {
     showLoadingSpinner();
 
     // Fetch the observations + metadata / non-computed
-    const observationsPlusMetadataNonComputed =
-      selectedBuildingsDataPointsSamplingRateAbbrevNonComputed.length === 0
+    const observationsPlusMetadataNonComputedArr =
+      selectedBuildingsDataPointsSamplingRateAbbrevNonComputedArr.length === 0
         ? [[], []]
         : await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
             BASE_URL,
             QUERY_PARAMS_COMBINED,
-            selectedBuildingsDataPointsSamplingRateAbbrevNonComputed
+            selectedBuildingsDataPointsSamplingRateAbbrevNonComputedArr
           );
 
     // Fetch the observations + metadata / computed (dT)
-    const observationsPlusMetadataComputed =
-      selectedBuildingsDataPointsSamplingRateAbbrevComputed.length === 0
+    const observationsPlusMetadataComputedArr =
+      selectedBuildingsDataPointsSamplingRateAbbrevComputedArr.length === 0
         ? [[], []]
         : await calculateVorlaufMinusRuecklaufTemperature(
             BASE_URL,
             QUERY_PARAMS_COMBINED,
             extractBuildingPlusSamplingRate(
-              selectedBuildingsDataPointsSamplingRateAbbrevComputed
+              selectedBuildingsDataPointsSamplingRateAbbrevComputedArr
             )
           );
 
     // Extract the combined arrays for observations and metadata / non-computed
     const [observationsNestedNonComputedArr, metadataNestedNonComputedArr] =
-      observationsPlusMetadataNonComputed;
+      observationsPlusMetadataNonComputedArr;
 
     // Extract the combined arrays for observations and metadata / computed (dT)
     const [observationsNestedComputedArr, metadataNestedComputedArr] =
-      observationsPlusMetadataComputed;
+      observationsPlusMetadataComputedArr;
 
     // Create a combined array of observations and metadata
     const observationsPlusMetadataCombined = [
@@ -246,7 +262,7 @@ const drawChartUsingSelectedOptions = async function () {
 
     // The formatted abbreviations array is nested
     const [selectedBuildingsDataPointsSamplingRateAbbrevArr] =
-      selectedBuildingsDataPointsSamplingRateAbbrev;
+      selectedBuildingsDataPointsSamplingRateAbbrevNestedArr;
 
     // Extract the formatted sampling rate string - used by ALL chart types
     const [, , selectedSamplingRateAbbrev] =
@@ -273,50 +289,38 @@ const drawChartUsingSelectedOptions = async function () {
         extractUniqueCalendarDatesFromTimestamp(observationsArr)
     );
 
-    selectedChartTypeArr.forEach((selectedChartType) => {
+    for (const selectedChartType of selectedChartTypeArr) {
       if (selectedChartType === "Heatmap") {
         // We are interested in raw observations
         if (
-          checkIfChartRequiresRawObservations(
-            selectedAggregationTypeArr,
-            selectedAggregationDuration
-          )
+          selectedAggregationOptionsAreValid &&
+          selectedBuildingDataPointsOptionsAreValid
         ) {
           drawHeatmapBasedOnSelectedOptions(
-            selectedBuildingsDataPointsSamplingRateAbbrev,
             observationsComboNestedArr,
             extractedFormattedDatastreamProperties
           );
-        } else {
-          throw new Error(
-            "This type of chart (Heatmap) does not support aggregated results"
-          );
         }
       }
+
       if (selectedChartType === "Scatter Plot") {
         // We are interested in raw observations
         if (
-          checkIfChartRequiresRawObservations(
-            selectedAggregationTypeArr,
-            selectedAggregationDuration
-          )
+          selectedAggregationOptionsAreValid &&
+          selectedBuildingDataPointsOptionsAreValid
         ) {
           drawScatterPlotFromChartSelection(
-            selectedBuildingsDataPointsSamplingRateAbbrev,
             observationsComboNestedArr,
             extractedFormattedDatastreamProperties
           );
-        } else {
-          throw new Error(
-            "This type of chart (Scatter Plot) does not support aggregated results"
-          );
         }
       }
+
       if (selectedChartType === "Line") {
         // We are interested in raw observations or aggregated observations
 
         // Raw observations
-        if (selectedAggregationTypeArr === "None (raw data)") {
+        if (selectedAggregationType === "None (raw data)") {
           // Create formatted array(s) for observations
           const formattedRawObservationsLineChartNestedArr =
             observationsComboNestedArr.map((observationsArr) =>
@@ -331,7 +335,7 @@ const drawChartUsingSelectedOptions = async function () {
         // Aggregated observations
         else {
           drawLineChartBasedOnSelectedAggregationOptions(
-            selectedAggregationTypeArr,
+            selectedAggregationType,
             selectedAggregationDuration,
             observationsAggregationNestedArr,
             selectedSamplingRateAbbrev,
@@ -340,11 +344,12 @@ const drawChartUsingSelectedOptions = async function () {
           );
         }
       }
+
       if (selectedChartType === "Column") {
         // We are interested in raw observations or aggregated observations
 
         // Raw observations
-        if (selectedAggregationTypeArr === "None (raw data)") {
+        if (selectedAggregationType === "None (raw data)") {
           // Create formatted array(s) for observations
           const formattedRawObservationsColumnChartNestedArr =
             observationsComboNestedArr.map((observationsArr) =>
@@ -359,7 +364,7 @@ const drawChartUsingSelectedOptions = async function () {
         // Aggregated observations
         else {
           drawColumnChartBasedOnSelectedAggregationOptions(
-            selectedAggregationTypeArr,
+            selectedAggregationType,
             selectedAggregationDuration,
             observationsAggregationNestedArr,
             selectedSamplingRateAbbrev,
@@ -368,7 +373,7 @@ const drawChartUsingSelectedOptions = async function () {
           );
         }
       }
-    });
+    }
   } catch (err) {
     console.error(err);
   } finally {
diff --git a/public/js/src_modules/chartColumn.mjs b/public/js/src_modules/chartColumn.mjs
index c54566d..8c6de14 100644
--- a/public/js/src_modules/chartColumn.mjs
+++ b/public/js/src_modules/chartColumn.mjs
@@ -2,10 +2,9 @@
 
 import {
   chartExportOptions,
-  createCombinedTextDelimitedByComma,
   createFullTitleForLineOrColumnChart,
+  createSubtitleForChart,
   createTooltipDateString,
-  extractSamplingRateFromDatastreamName,
 } from "./chartHelpers.mjs";
 
 /**
@@ -122,9 +121,7 @@ const drawColumnChartHighcharts = function (
     aggregationType
   );
 
-  const textChartSubtitle = `Sampling rate(s): ${createCombinedTextDelimitedByComma(
-    extractSamplingRateFromDatastreamName(datastreamNamesArr)
-  )}`;
+  const textChartSubtitle = createSubtitleForChart(datastreamNamesArr);
 
   Highcharts.chart("chart-column", {
     chart: {
diff --git a/public/js/src_modules/chartHeatmap.mjs b/public/js/src_modules/chartHeatmap.mjs
index a48c5bd..69d77f5 100644
--- a/public/js/src_modules/chartHeatmap.mjs
+++ b/public/js/src_modules/chartHeatmap.mjs
@@ -1,6 +1,10 @@
 "use strict";
 
-import { chartExportOptions } from "./chartHelpers.mjs";
+import {
+  chartExportOptions,
+  createTitleForHeatmap,
+  createSubtitleForHeatmap,
+} from "./chartHelpers.mjs";
 
 /**
  * Format the response from SensorThings API to make it suitable for use in a heatmap
@@ -73,6 +77,10 @@ const drawHeatMapHighcharts = function (
   const [PHENOMENON_NAME] = phenomenonNamesArr;
   const [PHENOMENON_SYMBOL] = unitOfMeasurementSymbolsArr;
 
+  const TEXT_CHART_TITLE = createTitleForHeatmap(phenomenonNamesArr);
+
+  const TEXT_CHART_SUBTITLE = createSubtitleForHeatmap(datastreamNamesArr);
+
   const {
     minObsValue: MINIMUM_VALUE_COLOR_AXIS,
     maxObsValue: MAXIMUM_VALUE_COLOR_AXIS,
@@ -89,13 +97,13 @@ const drawHeatMapHighcharts = function (
     },
 
     title: {
-      text: DATASTREAM_DESCRIPTION,
+      text: TEXT_CHART_TITLE,
       align: "left",
       x: 40,
     },
 
     subtitle: {
-      text: DATASTREAM_NAME,
+      text: TEXT_CHART_SUBTITLE,
       align: "left",
       x: 40,
     },
diff --git a/public/js/src_modules/chartHelpers.mjs b/public/js/src_modules/chartHelpers.mjs
index fb471f4..b079ae3 100644
--- a/public/js/src_modules/chartHelpers.mjs
+++ b/public/js/src_modules/chartHelpers.mjs
@@ -224,6 +224,34 @@ const createCombinedTextDelimitedByComma = function (metadataPropertiesArr) {
   return metadataPropertiesArr.join(", ");
 };
 
+/**
+ * Extracts the sampling rate substring from a datastream name string
+ * @param {Array} datastreamNamesArr An array of datastream name(s)
+ * @returns {Array} An array containing the sampling rate substring(s)
+ */
+const extractSamplingRateFromDatastreamName = function (datastreamNamesArr) {
+  // First split the Datastream name string based on a single space (" ").
+  // The sampling rate string is the last word in the resulting string.
+  // We then split the resulting string using the ':' character.
+  // Our interest is also in the last word in the resulting string
+  return datastreamNamesArr.map((datastreamName) =>
+    datastreamName.split(" ").pop().split(":").pop()
+  );
+};
+
+/**
+ * Extract the building ID substring from a datastream name string
+ *
+ * @param {Array} datastreamNamesArr An array of datastream name(s)
+ * @returns {Array} An array containing the building ID substring(s)
+ */
+const extractBuildingIdFromDatastreamName = function (datastreamNamesArr) {
+  // The building ID string is the first word in the Datastream name string
+  return datastreamNamesArr.map((datastreamName) =>
+    datastreamName.split(" ").shift()
+  );
+};
+
 /**
  * Create a partial string for a line chart or column chart title
  * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
@@ -245,7 +273,7 @@ const createPartialTitleForLineOrColumnChart = function (
 
 /**
  * Create a full string for a line chart or column chart title
- * @param {Array} datastreamNamesArr An array of datastream names as strings
+ * @param {Array} phenomenonNamesArr An array of phenomenon names as strings
  * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
  * @param {String} aggregationType The aggregation type as a string, either "sum" or "average"
  * @returns {String} Full string for chart title
@@ -269,6 +297,54 @@ const createFullTitleForLineOrColumnChart = function (
   )}: ${createCombinedTextDelimitedByComma(phenomenonNamesArr)}`;
 };
 
+/**
+ * Create a title for a heatmap
+ *
+ * @param {Array} phenomenonNamesArr An array of phenomenon names as strings
+ * @returns {String} A string that represents the heatmap title
+ */
+const createTitleForHeatmap = function (phenomenonNamesArr) {
+  return createCombinedTextDelimitedByComma(phenomenonNamesArr);
+};
+
+/**
+ * Create a subtitle for the following charts: column chart, line chart and scatter plot
+ *
+ * @param {Array} datastreamNamesArr An array of datastream name(s)
+ * @returns {String} A subtitle string
+ */
+const createSubtitleForChart = function (datastreamNamesArr) {
+  // Case 1: We only have one sampling rate string
+  if (datastreamNamesArr.length === 1) {
+    return `Sampling rate: ${createCombinedTextDelimitedByComma(
+      extractSamplingRateFromDatastreamName(datastreamNamesArr)
+    )}`;
+  }
+  // Case 2: We have more than one sampling rate string
+  else if (datastreamNamesArr.length > 1) {
+    return `Sampling rate(s): ${createCombinedTextDelimitedByComma(
+      extractSamplingRateFromDatastreamName(datastreamNamesArr)
+    )} respectively`;
+  }
+};
+
+/**
+ * Create a subtitle for a heatmap which is different from the subtitles used for the other
+ * types of charts, i.e. column charts, line charts and scatter plots
+ *
+ * @param {Array} datastreamNamesArr An array of datastream name(s)
+ * @returns {String} A subtitle string
+ */
+const createSubtitleForHeatmap = function (datastreamNamesArr) {
+  // Note: the `datastreamNamesArr` here contains only one element
+  // We use the `createCombinedTextDelimitedByComma` function to "spread" the resulting arrays
+  return `Building, Sampling rate: ${createCombinedTextDelimitedByComma(
+    extractBuildingIdFromDatastreamName(datastreamNamesArr)
+  )}, ${createCombinedTextDelimitedByComma(
+    extractSamplingRateFromDatastreamName(datastreamNamesArr)
+  )}`;
+};
+
 /**
  * Creates a date string that is used in a shared tooltip for a line or column chart
  * @param {Number} pointXAxisValue The x-axis value (Unix timestamp) which is common for a set of data points
@@ -286,18 +362,6 @@ const createTooltipDateString = function (
     return `${Highcharts.dateFormat("%b %Y", pointXAxisValue)}`;
 };
 
-/**
- * Extracts the sampling rate substring from a datastream name string
- * @param {Array} datastreamNamesArr An array of datastream name(s)
- * @returns {Array} An array containing the sampling rate substring(s)
- */
-const extractSamplingRateFromDatastreamName = function (datastreamNamesArr) {
-  // The sampling rate string is the last word in the Datastream name string
-  return datastreamNamesArr.map((datastreamName) =>
-    datastreamName.split(" ").pop()
-  );
-};
-
 /**
  * Remove the transparency (alpha channel) from a color
  * @param {String} rgbaColor A color expressed in RGBA format
@@ -313,8 +377,10 @@ export {
   createCombinedTextDelimitedByAmpersand,
   createCombinedTextDelimitedByComma,
   createFullTitleForLineOrColumnChart,
+  createTitleForHeatmap,
+  createSubtitleForChart,
+  createSubtitleForHeatmap,
   createTooltipDateString,
   convertHexColorToRGBColor,
-  extractSamplingRateFromDatastreamName,
   removeTransparencyFromColor,
 };
diff --git a/public/js/src_modules/chartLine.mjs b/public/js/src_modules/chartLine.mjs
index 26af913..ba9a2e1 100644
--- a/public/js/src_modules/chartLine.mjs
+++ b/public/js/src_modules/chartLine.mjs
@@ -3,8 +3,8 @@
 import {
   chartExportOptions,
   createFullTitleForLineOrColumnChart,
+  createSubtitleForChart,
   createTooltipDateString,
-  extractSamplingRateFromDatastreamName,
 } from "./chartHelpers.mjs";
 
 /**
@@ -114,9 +114,7 @@ const drawLineChartHighcharts = function (
     aggregationType
   );
 
-  const textChartSubtitle = `Sampling rate(s): ${createCombinedTextForLineChartTitles(
-    extractSamplingRateFromDatastreamName(datastreamNamesArr)
-  )}`;
+  const textChartSubtitle = createSubtitleForChart(datastreamNamesArr);
 
   // Create the array of series options object(s)
   const seriesOptionsArr = createSeriesOptionsForLineChart(
diff --git a/public/js/src_modules/chartScatterPlot.mjs b/public/js/src_modules/chartScatterPlot.mjs
index e1bd214..0cc2bdb 100644
--- a/public/js/src_modules/chartScatterPlot.mjs
+++ b/public/js/src_modules/chartScatterPlot.mjs
@@ -6,7 +6,7 @@ import {
   convertHexColorToRGBColor,
   createCombinedTextDelimitedByAmpersand,
   createCombinedTextDelimitedByComma,
-  extractSamplingRateFromDatastreamName,
+  createSubtitleForChart,
   removeTransparencyFromColor,
 } from "./chartHelpers.mjs";
 
@@ -200,9 +200,7 @@ const drawScatterPlotHighcharts = function (
   const CHART_TITLE =
     createCombinedTextForScatterPlotTitles(phenomenonNamesArr);
 
-  const CHART_SUBTITLE = `Sampling rate(s): ${createCombinedTextDelimitedByComma(
-    extractSamplingRateFromDatastreamName(datastreamNamesArr)
-  )}`;
+  const CHART_SUBTITLE = createSubtitleForChart(datastreamNamesArr);
 
   const X_AXIS_TITLE = createXAxisTitleTextScatterPlot(
     phenomenonNamesArr,
diff --git a/public/js/src_modules/dropDownListHelpers.mjs b/public/js/src_modules/dropDownListHelpers.mjs
index d70d714..f087cdd 100644
--- a/public/js/src_modules/dropDownListHelpers.mjs
+++ b/public/js/src_modules/dropDownListHelpers.mjs
@@ -109,19 +109,23 @@ const getSelectedOptionsFromAllDropDownLists = function () {
   );
 
   // Ensure that all the options have at least one selection
-  if (
-    selectedBuildingDataPointOptionsSplitArr.length === 0 ||
-    selectedAggregationOptionsArr.length === 0 ||
-    selectedSamplingRateArr.length === 0 ||
-    selectedChartTypeArr.length === 0
-  )
-    return;
+  if (selectedChartTypeArr.length === 0)
+    throw new Error("Please ensure that the chart type is selected");
+
+  if (selectedBuildingDataPointOptionsSplitArr.length === 0)
+    throw new Error("Please ensure that at least one data point is selected");
+
+  if (selectedSamplingRateArr.length === 0)
+    throw new Error("Please ensure that the aggregation type is selected");
+
+  if (selectedSamplingRateArr.length === 0)
+    throw new Error("Please ensure that the sampling rate is selected");
 
   return [
+    selectedChartTypeArr,
     selectedBuildingDataPointOptionsSplitArr,
     selectedAggregationOptionsArr,
     selectedSamplingRateArr,
-    selectedChartTypeArr,
   ];
 };
 
@@ -296,6 +300,82 @@ const checkIfChartRequiresRawObservations = function (
   }
 };
 
+/**
+ * Check if the selected building(s) + data point(s) options are valid for
+ * drawing either a heatmp or scatter plot. If these options are
+ * invalid, throw an error
+ *
+ * @param {Array} selectedChartTypeArr An array of string(s) representing the selected chart type(s)
+ * @param {Array} selectedBuildingsDataPointsSamplingRateAbbrevNestedArr An array that is made up of sub array(s) whose elements are strings representing the selected buildings, data points and sampling rates
+ * @returns {Boolean} true, if there are no errors thrown
+ */
+const checkIfSelectedBuildingDataPointsOptionsAreValid = function (
+  selectedChartTypeArr,
+  selectedBuildingsDataPointsSamplingRateAbbrevNestedArr
+) {
+  for (const selectedChartType of selectedChartTypeArr) {
+    // A heatmap can only visualize one data point
+    if (selectedChartType === "Heatmap") {
+      if (selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length === 1)
+        return true;
+      else if (
+        selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length > 1
+      ) {
+        throw new Error("A heatmap can only display one data point at a time");
+      }
+    }
+
+    // A scatter plot requires at least two data points
+    if (selectedChartType === "Scatter Plot") {
+      if (selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length >= 2)
+        return true;
+      else if (
+        selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length < 2
+      ) {
+        throw new Error("A scatter plot requires at least two data points");
+      }
+    }
+  }
+};
+
+/**
+ * Check if the selected aggregation type options are valid for
+ * drawing either a heatmp or scatter plot. If these options are
+ * invalid, throw an error
+ *
+ * @param {Array} selectedChartTypeArr An array of string(s) representing the selected chart type(s)
+ * @param {String} selectedAggregationType The selected aggregation type
+ * @param {String} selectedAggregationDuration The selected aggregation duration
+ * @returns {Boolean} true, if there are no errors thrown
+ */
+const checkIfSelectedAggregationOptionsAreValid = function (
+  selectedChartTypeArr,
+  selectedAggregationType,
+  selectedAggregationDuration
+) {
+  for (const selectedChartType of selectedChartTypeArr) {
+    if (
+      selectedChartType === "Heatmap" ||
+      selectedChartType === "Scatter Plot"
+    ) {
+      // For both chart types, we are interested in raw observations
+      if (
+        checkIfChartRequiresRawObservations(
+          selectedAggregationType,
+          selectedAggregationDuration
+        )
+      )
+        return true;
+      // Throw error if we attempt to use aggregated observations
+      else {
+        throw new Error(
+          "The selected chart type does not support aggregated results"
+        );
+      }
+    }
+  }
+};
+
 /**
  * Get the abbreviated form of building IDs, phenomenon names and sensor sampling rates
  * @param {String} buildingFullForm A string representation of the full form of a building ID
@@ -380,9 +460,14 @@ const getBuildingSensorSamplingRateAbbreviation = function (
 const getAbbreviationsForSelectedOptionsFromAllDropDownLists = function (
   allSelectedOptionsArr
 ) {
-  // Note: The sampling rate array is the third array, therefore we skip one element
-  const [selectedBuildingDataPointOptionsSplitArr, , selectedSamplingRateArr] =
-    allSelectedOptionsArr;
+  // Note: The buildings + data points array is the second element and
+  // the sampling rate array is the fourth element, therefore we skip the first and third elementa
+  const [
+    ,
+    selectedBuildingDataPointOptionsSplitArr,
+    ,
+    selectedSamplingRateArr,
+  ] = allSelectedOptionsArr;
 
   // The building is the first element
   const selectedBuildingsArr = selectedBuildingDataPointOptionsSplitArr.map(
@@ -417,6 +502,7 @@ export {
   deleteTemperatureDifferenceOptions,
   extractTemperatureDifferenceOptions,
   extractBuildingPlusSamplingRate,
-  checkIfChartRequiresRawObservations,
+  checkIfSelectedBuildingDataPointsOptionsAreValid,
+  checkIfSelectedAggregationOptionsAreValid,
   getAbbreviationsForSelectedOptionsFromAllDropDownLists,
 };
diff --git a/public/js/src_modules/dropDownListProcessing.mjs b/public/js/src_modules/dropDownListProcessing.mjs
index 28d0ead..47715ca 100644
--- a/public/js/src_modules/dropDownListProcessing.mjs
+++ b/public/js/src_modules/dropDownListProcessing.mjs
@@ -29,75 +29,58 @@ import { extractPropertiesFromFormattedDatastreamMetadata } from "./fetchedDataP
 /**
  * Draw a heatmap based on the selected options from a drop-down list
  *
- * @param {Array} selectedBuildingsDataPointsSamplingRateAbbrevArr An array which contains one or more nested arrays of abbreviations of building(s), data point(s) and sampling rate(s)
  * @param {Array} observationsComboNestedArr An array that contains non-computed (raw) observations and computed (temperature difference, dT) observations
  * @param {Object} extractedFormattedDatastreamProperties An object that contains array(s) of formatted Datastream properties
  * @returns {undefined} undefined
  */
 const drawHeatmapBasedOnSelectedOptions = function (
-  selectedBuildingsDataPointsSamplingRateAbbrevArr,
   observationsComboNestedArr,
   extractedFormattedDatastreamProperties
 ) {
-  if (selectedBuildingsDataPointsSamplingRateAbbrevArr.length === 1) {
-    // Create formatted array(s) for observations
-    const formattedObservationsHeatMapNestedArr =
-      observationsComboNestedArr.map((observationsArr) =>
-        formatSensorThingsApiResponseForHeatMap(observationsArr)
-      );
-
-    // Note: The resulting array is nested and is not suitable for heatmap,
-    // extract the nested array
-    const [formattedObservationsHeatMapArr] =
-      formattedObservationsHeatMapNestedArr;
-
-    drawHeatMapHighcharts(
-      formattedObservationsHeatMapArr,
-      extractedFormattedDatastreamProperties
-    );
-  } else if (selectedBuildingsDataPointsSamplingRateAbbrevArr.length < 1) {
-    throw new Error("Please select at least one data point");
-  } else {
-    throw new Error(
-      "This type of chart (Heatmap) can only display one data point at a time"
-    );
-  }
+  // Create formatted array(s) for observations
+  const formattedObservationsHeatMapNestedArr = observationsComboNestedArr.map(
+    (observationsArr) =>
+      formatSensorThingsApiResponseForHeatMap(observationsArr)
+  );
+
+  // Note: The resulting array is nested and is not suitable for heatmap,
+  // extract the nested array
+  const [formattedObservationsHeatMapArr] =
+    formattedObservationsHeatMapNestedArr;
+
+  drawHeatMapHighcharts(
+    formattedObservationsHeatMapArr,
+    extractedFormattedDatastreamProperties
+  );
 };
 
 /**
  * Draw a scatter plot based on the selected options from a drop-down list
  *
- * @param {Array} selectedBuildingsDataPointsSamplingRateAbbrevArr An array which contains one or more nested arrays of abbreviations of building(s), data point(s) and sampling rate(s)
  * @param {Array} observationsComboNestedArr An array that contains non-computed (raw) observations and computed (temperature difference, dT) observations
  * @param {Object} extractedFormattedDatastreamProperties An object that contains array(s) of formatted Datastream properties
  * @returns {undefined} undefined
  */
 const drawScatterPlotFromChartSelection = function (
-  selectedBuildingsDataPointsSamplingRateAbbrevArr,
   observationsComboNestedArr,
   extractedFormattedDatastreamProperties
 ) {
-  // Check the length of buildings + data points + sampling rate array
-  if (selectedBuildingsDataPointsSamplingRateAbbrevArr.length >= 2) {
-    // Extract values for x-axis and y-axis
-    // x-axis values are first element of nested observations array
-    const [obsXAxisArr] = observationsComboNestedArr.slice(0, 1);
-    // y-axis values are rest of elements of nested observations array
-    const obsYAxisNestedArr = observationsComboNestedArr.slice(1);
-
-    // Create formatted array(s) for observations
-    const formattedObservationsScatterPlotArr = obsYAxisNestedArr.map(
-      (obsYAxisArr) =>
-        formatSensorThingsApiResponseForScatterPlot(obsXAxisArr, obsYAxisArr)
-    );
-
-    drawScatterPlotHighcharts(
-      formattedObservationsScatterPlotArr,
-      extractedFormattedDatastreamProperties
-    );
-  } else {
-    throw new Error("A scatter plot chart requires at least two data points");
-  }
+  // Extract values for x-axis and y-axis
+  // x-axis values are first element of nested observations array
+  const [obsXAxisArr] = observationsComboNestedArr.slice(0, 1);
+  // y-axis values are rest of elements of nested observations array
+  const obsYAxisNestedArr = observationsComboNestedArr.slice(1);
+
+  // Create formatted array(s) for observations
+  const formattedObservationsScatterPlotArr = obsYAxisNestedArr.map(
+    (obsYAxisArr) =>
+      formatSensorThingsApiResponseForScatterPlot(obsXAxisArr, obsYAxisArr)
+  );
+
+  drawScatterPlotHighcharts(
+    formattedObservationsScatterPlotArr,
+    extractedFormattedDatastreamProperties
+  );
 };
 
 /**
-- 
GitLab