diff --git a/index.html b/index.html
index d51f644af8dcc2559ec2c264a0ab8342ed59b7ed..a355149988562ee516c348ae275fe3cc08229d5a 100644
--- a/index.html
+++ b/index.html
@@ -69,7 +69,6 @@
     Custom JS -->
     <script defer type="module" src="js/appCesium.js"></script>
     <script defer type="module" src="js/appChart.js"></script>
-    <script defer type="module" src="js/dropDownList.js"></script>
   </head>
   <body class="sb-nav-fixed">
     <nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
@@ -131,6 +130,21 @@
                         <label for="droneMode">Drone View</label>
                       </div>
                       <br /> -->
+                      <div id="drop-down--chart-type-parent">
+                        <span><strong>Chart type</strong></span>
+                        <div class="nowrap">
+                          <!-- We need the `multiple` attribute for the dropdowns even if 
+                          we do not need to support multiple selections. This seems to 
+                          be a quirk of the `vanillaSelectBox` library -->
+                          <select id="drop-down--chart-type" multiple>
+                            <option>Column</option>
+                            <option>Line</option>
+                            <option>Heatmap</option>
+                            <option>Scatter Plot</option>
+                          </select>
+                        </div>
+                      </div>
+                      <br />
                       <div id="drop-down--bldg-data-point-parent">
                         <span><strong>Building(s), Data Point(s)</strong></span>
                         <div class="nowrap">
@@ -140,6 +154,9 @@
                             size="5"
                           >
                             <!-- Note: The values of the option elements have to be unique  -->
+                            <optgroup label="Other">
+                              <option>Außentemp</option>
+                            </optgroup>
                             <optgroup label="Bau 101">
                               <option>101/VL</option>
                               <option>101/RL</option>
@@ -174,9 +191,6 @@
                               <option>225/Energie</option>
                               <option>225/Energie_VERBR</option>
                             </optgroup>
-                            <optgroup label="Other">
-                              <option>Außentemp</option>
-                            </optgroup>
                           </select>
                         </div>
                       </div>
@@ -216,21 +230,6 @@
                         </div>
                       </div>
                       <br />
-                      <div id="drop-down--chart-type-parent">
-                        <span><strong>Chart type</strong></span>
-                        <div class="nowrap">
-                          <!-- We need the `multiple` attribute for the dropdowns even if 
-                          we do not need to support multiple selections. This seems to 
-                          be a quirk of the `vanillaSelectBox` library -->
-                          <select id="drop-down--chart-type" multiple>
-                            <option>Column</option>
-                            <option>Line</option>
-                            <option>Heatmap</option>
-                            <option>Scatter Plot</option>
-                          </select>
-                        </div>
-                      </div>
-                      <br />
                       <button id="btn-draw-chart">Draw Chart</button>
                       <!-- <span><strong>Display Options</strong></span>
                       <div class="nowrap">
diff --git a/public/js/appChart.js b/public/js/appChart.js
index 4a49952384c662bd202b7b5da1951d7b21867f3b..638884c6019c7cbe3cd4b4f22cc517bf1cd83a96 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -5,765 +5,380 @@ import {
   QUERY_PARAMS_COMBINED,
 } from "./src_modules/baseUrlPlusQueryParams.mjs";
 
-import {
-  formatSensorThingsApiResponseForLineOrColumnChart,
-  drawLineChartHighcharts,
-} from "./src_modules/chartLine.mjs";
+import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs";
 
-import {
-  formatSensorThingsApiResponseForHeatMap,
-  drawHeatMapHighcharts,
-} from "./src_modules/chartHeatmap.mjs";
+import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs";
 
 import {
-  formatSensorThingsApiResponseForScatterPlot,
-  drawScatterPlotHighcharts,
-} from "./src_modules/chartScatterPlot.mjs";
+  formatDatastreamMetadataForChart,
+  extractPropertiesFromFormattedDatastreamMetadata,
+} from "./src_modules/fetchedDataProcessing.mjs";
 
 import {
-  formatAggregationResultForColumnChart,
-  drawColumnChartHighcharts,
-} from "./src_modules/chartColumn.mjs";
+  formatSensorThingsApiResponseForLineOrColumnChart,
+  drawLineChartHighcharts,
+} from "./src_modules/chartLine.mjs";
 
-import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs";
+import { drawColumnChartHighcharts } from "./src_modules/chartColumn.mjs";
 
 import {
-  formatDatastreamMetadataForChart,
-  extractPropertiesFromFormattedDatastreamMetadata,
-} from "./src_modules/fetchedDataProcessing.mjs";
+  showLoadingSpinner,
+  hideLoadingSpinner,
+} from "./src_modules/loadingIndicator.mjs";
 
-import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs";
+import { vanillaSelectBox } from "./thirdparty/vanillaSelectBox.mjs";
 
 import {
   extractObservationsWithinDatesInterval,
   extractUniqueCalendarDatesFromTimestamp,
-  extractUniqueCalendarMonthsFromCalendarDates,
 } from "./src_modules/aggregateHelpers.mjs";
 
 import {
-  calculateMinimumObservationValuesWithinInterval,
-  calculateMaximumObservationValuesWithinInterval,
-  calculateSumOfObservationValuesWithinInterval,
-  calculateAverageOfObservationValuesWithinInterval,
-} from "./src_modules/aggregate.mjs";
-
-/**
- * Test plotting of temp difference (dT) using heatmap
- */
-const drawHeatmapHCUsingTempDifference = async function () {
-  try {
-    const [observationsTemperatureDiff225Arr, metadataTemperatureDiff225Arr] =
-      await calculateVorlaufMinusRuecklaufTemperature(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        "225",
-        "60min"
-      );
-
-    // We want to have nested arrays, so as to mimick the nested responses we get from fetching observations + metadata
-    const observationsTemperatureDiff225NestedArr = [
-      observationsTemperatureDiff225Arr,
-    ];
+  splitMultipleOptionsTextDelimitedBySlash,
+  getSelectedOptionsFromAllDropDownLists,
+  checkIfSelectedOptionsContainTemperatureDifference,
+  deleteTemperatureDifferenceOptions,
+  extractTemperatureDifferenceOptions,
+  extractBuildingPlusSamplingRate,
+  checkIfChartRequiresRawObservations,
+  getAbbreviationsForSelectedOptionsFromAllDropDownLists,
+} from "./src_modules/dropDownListHelpers.mjs";
 
-    const metadataTemperatureDiff225NestedArr = [metadataTemperatureDiff225Arr];
-
-    // Format the observations
-    const formattedTempDiff225NestedArr =
-      observationsTemperatureDiff225NestedArr.map((obsArr) =>
-        formatSensorThingsApiResponseForHeatMap(obsArr)
-      );
-
-    // Format the metadata
-    const formattedTempDiff225MetadataNestedArr =
-      metadataTemperatureDiff225NestedArr.map((metadataObj) =>
-        formatDatastreamMetadataForChart(metadataObj)
-      );
-
-    // Extract the formatted metadata properties
-    const extractedFormattedTempDiff225Properties =
-      extractPropertiesFromFormattedDatastreamMetadata(
-        formattedTempDiff225MetadataNestedArr,
-        false
-      );
-
-    // First need to extract the formatted observations from the nested array
-    // Heatmap only needs one set of formatted observation values
-    drawHeatMapHighcharts(
-      ...formattedTempDiff225NestedArr,
-      extractedFormattedTempDiff225Properties
-    );
-  } catch (err) {
-    console.error(err);
-  }
-};
+import {
+  drawHeatmapBasedOnSelectedOptions,
+  drawScatterPlotFromChartSelection,
+  drawLineChartBasedOnSelectedAggregationOptions,
+  drawColumnChartBasedOnSelectedAggregationOptions,
+} from "./src_modules/dropDownListProcessing.mjs";
 
 /**
- * Test drawing of scatter plot chart
+ * Use the `vanillaDropDown` library to style the buildings & data points drop down list
+ *
+ * @returns {undefined}
  */
-const drawScatterPlotHCTest2 = async function () {
-  try {
-    const sensorsOfInterestNestedArr = [
-      ["weather_station_521", "outside_temp", "60min"],
-      ["225", "vl", "60min"],
-      ["125", "rl", "60min"],
-    ];
-
-    const observationsPlusMetadata =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        sensorsOfInterestNestedArr
-      );
-
-    // Extract the combined arrays for observations and metadata
-    const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-    // Extract values for x-axis and y-axis
-    // x-axis values are first element of nested observations array
-    const [obsXAxisArr] = observationsNestedArr.slice(0, 1);
-    // y-axis values are rest of elements of nested observations array
-    const obsYAxisNestedArr = observationsNestedArr.slice(1);
-
-    // Create formatted array(s) for observations
-    const formattedObservationsArr = obsYAxisNestedArr.map((obsYAxisArr) =>
-      formatSensorThingsApiResponseForScatterPlot(obsXAxisArr, obsYAxisArr)
-    );
-
-    // Create formatted array(s) for metadata
-    const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj, false)
-    );
-
-    // Extract the formatted metadata properties
-    const extractedFormattedDatastreamProperties =
-      extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr,
-        false
-      );
-
-    drawScatterPlotHighcharts(
-      formattedObservationsArr,
-      extractedFormattedDatastreamProperties
-    );
-  } catch (err) {
-    console.error(err);
-  }
+const styleBuildingsDataPointsDropDown = function () {
+  // Create our dropdown list using `vanillaSelectBox`; supports the selection of multiple options
+  new vanillaSelectBox("#drop-down--bldg-data-point", {
+    "disableSelectAll": true,
+    "maxSelect": 5,
+    "placeHolder": "--Select--",
+    "search": false,
+  });
 };
 
 /**
- * Test drawing of line chart with multiple series
+ * Use the `vanillaDropDown` library to style the aggregation type drop down list
+ *
+ * @returns {undefined}
  */
-const testLineChartMultipleSeries = async function () {
-  try {
-    const sensorsOfInterestNestedArr = [
-      ["225", "vl", "60min"],
-      ["125", "rl", "60min"],
-      ["weather_station_521", "outside_temp", "60min"],
-    ];
-
-    const observationsPlusMetadataArr =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        sensorsOfInterestNestedArr
-      );
-
-    // Extract the observations and metadata arrays of arrays
-    const [observationsNestedArr, metadataNestedArr] =
-      observationsPlusMetadataArr;
-
-    // Format the observations
-    const formattedObservationsNestedArr = observationsNestedArr.map(
-      (observationsArr) =>
-        formatSensorThingsApiResponseForLineOrColumnChart(observationsArr)
-    );
-
-    // Format the metadata
-    const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) =>
-      formatDatastreamMetadataForChart(metadataArr)
-    );
-
-    // Extract the formatted metadata properties
-    const extractedFormattedDatastreamProperties =
-      extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr,
-        false
-      );
-
-    drawLineChartHighcharts(
-      formattedObservationsNestedArr,
-      extractedFormattedDatastreamProperties
-    );
-  } catch (err) {
-    console.error(err);
-  }
+const styleAggregationDropDown = function () {
+  // Create our dropdown list using `vanillaSelectBox`
+  new vanillaSelectBox("#drop-down--aggregation-type", {
+    "disableSelectAll": true,
+    "maxSelect": 1,
+    "placeHolder": "--Select--",
+    "search": false,
+  });
 };
 
 /**
- * Test drawing of column chart using aggregation / sum result - monthly
+ * Use the `vanillaDropDown` library to style the third sampling rate down list
+ *
+ * @returns {undefined}
  */
-const drawColumnChartMonthlySumTest = async function () {
-  try {
-    const sensorsOfInterestNestedArr = [
-      ["125", "vl", "60min"],
-      ["225", "vl", "60min"],
-    ];
-
-    const observationsPlusMetadata =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        sensorsOfInterestNestedArr
-      );
-
-    // Extract the observations and metadata for each sensor
-    // Array elements in same order as input array
-    const [obsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-    // User-specified start date and end date
-    const startDate = "2020-02-01";
-    const endDate = "2020-05-31";
-
-    // Extract observations within the user-specified start and end date
-    const observationsNestedArr = obsNestedArr.map((obsArr) =>
-      extractObservationsWithinDatesInterval(
-        obsArr,
-        "60min",
-        startDate,
-        endDate
-      )
-    );
-
-    // Unique calendar dates
-    const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
-      (observationsArr) =>
-        extractUniqueCalendarDatesFromTimestamp(observationsArr)
-    );
-
-    // Unique calendar months
-    const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
-      (uniqueCalendarDatesArr) =>
-        extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
-    );
-
-    // Calculate sum of values of observations - monthly
-    const observationsSumMonthlyNestedArr =
-      calculateSumOfObservationValuesWithinInterval(
-        observationsNestedArr,
-        "60min",
-        uniqueCalendarMonthsNestedArr,
-        "monthly"
-      );
-
-    // Format the observations
-    const formattedObservationsSumMonthlyNestedArr =
-      observationsSumMonthlyNestedArr.map((obsSumMonthlyArr, i) =>
-        formatAggregationResultForColumnChart(
-          uniqueCalendarMonthsNestedArr[i],
-          obsSumMonthlyArr
-        )
-      );
-
-    // Format the metadata
-    const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
-    );
-
-    // Extract the formatted metadata properties
-    const extractedFormattedDatastreamProperties =
-      extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr,
-        true,
-        "monthly",
-        "sum"
-      );
-
-    drawColumnChartHighcharts(
-      formattedObservationsSumMonthlyNestedArr,
-      extractedFormattedDatastreamProperties
-    );
-  } catch (err) {
-    console.error(err);
-  }
+const styleSamplingRateDropDown = function () {
+  // Create our dropdown list using `vanillaSelectBox`
+  new vanillaSelectBox("#drop-down--sampling-rate", {
+    "disableSelectAll": true,
+    "maxSelect": 1,
+    "placeHolder": "--Select--",
+    "search": false,
+  });
 };
 
 /**
- * Test drawing of column chart using aggregation / sum result - daily
+ * Use the `vanillaDropDown` library to style the chart type drop down list
+ *
+ * @returns {undefined}
  */
-const drawColumnChartDailySumTest = async function () {
-  try {
-    const sensorsOfInterestNestedArr = [
-      ["125", "vl", "60min"],
-      ["225", "vl", "60min"],
-    ];
-
-    const observationsPlusMetadata =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        sensorsOfInterestNestedArr
-      );
-
-    // Extract the observations and metadata for each sensor
-    // Array elements in same order as input array
-    const [obsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-    // User-specified start date and end date
-    const startDate = "2020-02-01";
-    const endDate = "2020-05-31";
-
-    // Extract observations within the user-specified start and end date
-    const observationsNestedArr = obsNestedArr.map((obsArr) =>
-      extractObservationsWithinDatesInterval(
-        obsArr,
-        "60min",
-        startDate,
-        endDate
-      )
-    );
-
-    // Unique calendar dates
-    const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
-      (observationsArr) =>
-        extractUniqueCalendarDatesFromTimestamp(observationsArr)
-    );
-
-    // Calculate sum of values of observations - daily
-    const observationsSumDailyNestedArr =
-      calculateSumOfObservationValuesWithinInterval(
-        observationsNestedArr,
-        "60min",
-        uniqueCalendarDatesNestedArr,
-        "daily"
-      );
-
-    // Format the observations - daily
-    const formattedObservationsSumDailyNestedArr =
-      observationsSumDailyNestedArr.map((obsSumDailyArr, i) =>
-        formatAggregationResultForColumnChart(
-          uniqueCalendarDatesNestedArr[i],
-          obsSumDailyArr
-        )
-      );
-
-    // Format the metadata
-    const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
-    );
-
-    // Extract the formatted metadata properties
-    const extractedFormattedDatastreamProperties =
-      extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr,
-        true,
-        "daily",
-        "sum"
-      );
-
-    drawColumnChartHighcharts(
-      formattedObservationsSumDailyNestedArr,
-      extractedFormattedDatastreamProperties
-    );
-  } catch (err) {
-    console.error(err);
-  }
+const styleChartTypeDropDown = function () {
+  // Create our dropdown list using `vanillaSelectBox`
+  new vanillaSelectBox("#drop-down--chart-type", {
+    "disableSelectAll": true,
+    "maxSelect": 1,
+    "placeHolder": "--Select--",
+    "search": false,
+  });
 };
 
 /**
- * Test drawing of column chart using raw observations
+ * Callback function that wraps the logic of populating the linked drop down lists.
+ * Will run on `DOMContentLoaded` event
+ *
+ * @returns {undefined}
  */
-const drawColumnChartNonAggregationTest = async function () {
-  try {
-    const sensorsOfInterestNestedArr = [
-      ["125", "vl", "60min"],
-      ["225", "vl", "60min"],
-    ];
-
-    const observationsPlusMetadata =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        sensorsOfInterestNestedArr
-      );
-
-    // Extract the observations and metadata for each sensor
-    // Array elements in same order as input array
-    const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-    // Format the observations
-    const formattedObservationsNestedArr = observationsNestedArr.map(
-      (observationsArr) =>
-        formatSensorThingsApiResponseForLineOrColumnChart(observationsArr)
-    );
-
-    // Format the metadata
-    const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) =>
-      formatDatastreamMetadataForChart(metadataArr)
-    );
-
-    // Extract the formatted metadata properties
-    const extractedFormattedDatastreamProperties =
-      extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr,
-        false
-      );
-
-    drawColumnChartHighcharts(
-      formattedObservationsNestedArr,
-      extractedFormattedDatastreamProperties
-    );
-  } catch (err) {
-    console.error(err);
-  }
+const afterDocumentLoads = function () {
+  styleBuildingsDataPointsDropDown();
+  styleAggregationDropDown();
+  styleSamplingRateDropDown();
+  styleChartTypeDropDown();
 };
 
 /**
- * Test drawing of line chart using aggregation / average result - monthly
+ * Callback function that draws a chart using options from the drop-down list that
+ * have been selected by a user.
+ * Will be run when the user clicks a button
+ *
+ * @async
+ * @returns {undefined} undefined
  */
-const drawLineChartMonthlyAverageTest = async function () {
+const drawChartUsingSelectedOptions = async function () {
   try {
-    const sensorsOfInterestNestedArr = [
-      ["125", "vl", "60min"],
-      ["225", "vl", "60min"],
-    ];
+    const selectedOptionsAllDropDownLists =
+      getSelectedOptionsFromAllDropDownLists();
 
-    const observationsPlusMetadata =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        sensorsOfInterestNestedArr
-      );
+    // 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] =
+      selectedOptionsAllDropDownLists;
 
-    // Extract the observations and metadata for each sensor
-    // Array elements in same order as input array
-    const [obsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-    // User-specified start date and end date
-    const startDate = "2020-02-01";
-    const endDate = "2020-05-31";
-
-    // Extract observations within the user-specified start and end date
-    const observationsNestedArr = obsNestedArr.map((obsArr) =>
-      extractObservationsWithinDatesInterval(
-        obsArr,
-        "60min",
-        startDate,
-        endDate
-      )
-    );
-
-    // Unique calendar dates
-    const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
-      (observationsArr) =>
-        extractUniqueCalendarDatesFromTimestamp(observationsArr)
-    );
-
-    // Unique calendar months
-    const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
-      (uniqueCalendarDatesArr) =>
-        extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
-    );
-
-    // Calculate average of values of observations - monthly
-    const observationsAverageMonthlyNestedArr =
-      calculateAverageOfObservationValuesWithinInterval(
-        observationsNestedArr,
-        "60min",
-        uniqueCalendarMonthsNestedArr,
-        "monthly"
+    // Create an array of aggregation type and duration
+    const selectedAggregationTypeDurationSplitNestedArr =
+      splitMultipleOptionsTextDelimitedBySlash(
+        selectedAggregationTypeDurationArr
       );
 
-    // Format the observations
-    const formattedObservationsAverageMonthlyNestedArr =
-      observationsAverageMonthlyNestedArr.map((obsAverageMonthlyArr, i) =>
-        formatAggregationResultForColumnChart(
-          uniqueCalendarMonthsNestedArr[i],
-          obsAverageMonthlyArr
-        )
-      );
+    // Separate the aggregation type and the aggregation duration strings
+    const [selectedAggregationTypeDurationSplitArr] =
+      selectedAggregationTypeDurationSplitNestedArr;
 
-    // Format the metadata
-    const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
-    );
+    const [selectedAggregationTypeArr, selectedAggregationDuration] =
+      selectedAggregationTypeDurationSplitArr;
 
-    // Extract the formatted metadata properties
-    const extractedFormattedDatastreamProperties =
-      extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr,
-        true,
-        "monthly",
-        "average"
+    // Array of building(s) + data point(s) + sampling rate
+    const selectedBuildingsDataPointsSamplingRateAbbrev =
+      getAbbreviationsForSelectedOptionsFromAllDropDownLists(
+        selectedOptionsAllDropDownLists
       );
 
-    drawLineChartHighcharts(
-      formattedObservationsAverageMonthlyNestedArr,
-      extractedFormattedDatastreamProperties
-    );
-  } catch (err) {
-    console.error(err);
-  }
-};
-
-/**
- * Test drawing of line chart using aggregation / average result - daily
- */
-const drawLineChartDailyAverageTest = async function () {
-  try {
-    const sensorsOfInterestNestedArr = [
-      ["125", "vl", "60min"],
-      ["225", "vl", "60min"],
+    // Create copies of the arrays of building(s) + data point(s) + sampling rate
+    const selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy = [
+      ...selectedBuildingsDataPointsSamplingRateAbbrev,
     ];
 
-    const observationsPlusMetadata =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        sensorsOfInterestNestedArr
-      );
-
-    // Extract the observations and metadata for each sensor
-    // Array elements in same order as input array
-    const [obsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-    // User-specified start date and end date
-    const startDate = "2020-02-01";
-    const endDate = "2020-05-31";
-
-    // Extract observations within the user-specified start and end date
-    const observationsNestedArr = obsNestedArr.map((obsArr) =>
-      extractObservationsWithinDatesInterval(
-        obsArr,
-        "60min",
-        startDate,
-        endDate
-      )
-    );
-
-    // Unique calendar dates
-    const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
-      (observationsArr) =>
-        extractUniqueCalendarDatesFromTimestamp(observationsArr)
-    );
-
-    // Calculate average of values of observations - daily
-    const observationsAverageDailyNestedArr =
-      calculateAverageOfObservationValuesWithinInterval(
-        observationsNestedArr,
-        "60min",
-        uniqueCalendarDatesNestedArr,
-        "daily"
-      );
-
-    // Format the observations - daily
-    const formattedObservationsAverageDailyNestedArr =
-      observationsAverageDailyNestedArr.map((obsAverageDailyArr, i) =>
-        formatAggregationResultForColumnChart(
-          uniqueCalendarDatesNestedArr[i],
-          obsAverageDailyArr
-        )
-      );
-
-    // Format the metadata
-    const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
-    );
-
-    // Extract the formatted metadata properties
-    const extractedFormattedDatastreamProperties =
-      extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr,
-        true,
-        "daily",
-        "average"
-      );
-
-    drawLineChartHighcharts(
-      formattedObservationsAverageDailyNestedArr,
-      extractedFormattedDatastreamProperties
-    );
-  } catch (err) {
-    console.error(err);
-  }
-};
-
-/**
- * Test drawing of line chart using aggregation / minimum result - daily
- */
-const drawLineChartDailyMinTest = async function () {
-  try {
-    const sensorsOfInterestNestedArr = [
-      ["125", "vl", "60min"],
-      ["225", "vl", "60min"],
+    const selectedBuildingsDataPointsSamplingRateAbbrevComputedCopy = [
+      ...selectedBuildingsDataPointsSamplingRateAbbrev,
     ];
 
-    const observationsPlusMetadata =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        sensorsOfInterestNestedArr
-      );
-
-    // Extract the observations and metadata for each sensor
-    // Array elements in same order as input array
-    const [obsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-    // User-specified start date and end date
-    const startDate = "2020-02-01";
-    const endDate = "2020-05-31";
-
-    // Extract observations within the user-specified start and end date
-    const observationsNestedArr = obsNestedArr.map((obsArr) =>
-      extractObservationsWithinDatesInterval(
-        obsArr,
-        "60min",
-        startDate,
-        endDate
+    // Check if we have non-computed
+    const selectedBuildingsDataPointsSamplingRateAbbrevNonComputed =
+      checkIfSelectedOptionsContainTemperatureDifference(
+        selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy
       )
-    );
-
-    // Unique calendar dates
-    const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
-      (observationsArr) =>
-        extractUniqueCalendarDatesFromTimestamp(observationsArr)
-    );
-
-    // Calculate average of values of observations - daily
-    const observationsMinimumDailyNestedArr =
-      calculateMinimumObservationValuesWithinInterval(
-        observationsNestedArr,
-        "60min",
-        uniqueCalendarDatesNestedArr,
-        "daily"
-      );
+        ? deleteTemperatureDifferenceOptions(
+            selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy
+          )
+        : selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy;
+
+    // Check if we have computed / dT
+    const selectedBuildingsDataPointsSamplingRateAbbrevComputed =
+      checkIfSelectedOptionsContainTemperatureDifference(
+        selectedBuildingsDataPointsSamplingRateAbbrevComputedCopy
+      )
+        ? extractTemperatureDifferenceOptions(
+            selectedBuildingsDataPointsSamplingRateAbbrevComputedCopy
+          )
+        : [];
+
+    // Display the loading indicator
+    showLoadingSpinner();
+
+    // Fetch the observations + metadata / non-computed
+    const observationsPlusMetadataNonComputed =
+      selectedBuildingsDataPointsSamplingRateAbbrevNonComputed.length === 0
+        ? [[], []]
+        : await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
+            BASE_URL,
+            QUERY_PARAMS_COMBINED,
+            selectedBuildingsDataPointsSamplingRateAbbrevNonComputed
+          );
+
+    // Fetch the observations + metadata / computed (dT)
+    const observationsPlusMetadataComputed =
+      selectedBuildingsDataPointsSamplingRateAbbrevComputed.length === 0
+        ? [[], []]
+        : await calculateVorlaufMinusRuecklaufTemperature(
+            BASE_URL,
+            QUERY_PARAMS_COMBINED,
+            extractBuildingPlusSamplingRate(
+              selectedBuildingsDataPointsSamplingRateAbbrevComputed
+            )
+          );
+
+    // Extract the combined arrays for observations and metadata / non-computed
+    const [observationsNestedNonComputedArr, metadataNestedNonComputedArr] =
+      observationsPlusMetadataNonComputed;
+
+    // Extract the combined arrays for observations and metadata / computed (dT)
+    const [observationsNestedComputedArr, metadataNestedComputedArr] =
+      observationsPlusMetadataComputed;
+
+    // Create a combined array of observations and metadata
+    const observationsPlusMetadataCombined = [
+      [...observationsNestedNonComputedArr, ...observationsNestedComputedArr],
+      [...metadataNestedNonComputedArr, ...metadataNestedComputedArr],
+    ];
 
-    // Format the observations - daily
-    const formattedObservationsMinimumDailyNestedArr =
-      observationsMinimumDailyNestedArr.map((obsMinDailyArr, i) =>
-        formatAggregationResultForColumnChart(
-          uniqueCalendarDatesNestedArr[i],
-          obsMinDailyArr
-        )
-      );
+    const [observationsComboNestedArr, metadataComboNestedArr] =
+      observationsPlusMetadataCombined;
 
-    // Format the metadata
-    const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
+    // Create formatted array(s) for metadata - used by ALL chart types
+    const formattedMetadataNestedArr = metadataComboNestedArr.map(
+      (metadataObj) => formatDatastreamMetadataForChart(metadataObj)
     );
 
-    // Extract the formatted metadata properties
+    // Extract the formatted metadata properties - used by ALL chart types
     const extractedFormattedDatastreamProperties =
       extractPropertiesFromFormattedDatastreamMetadata(
         formattedMetadataNestedArr,
-        true,
-        "daily",
-        "minimum"
+        false
       );
 
-    drawLineChartHighcharts(
-      formattedObservationsMinimumDailyNestedArr,
-      extractedFormattedDatastreamProperties
-    );
-  } catch (err) {
-    console.error(err);
-  }
-};
+    // The formatted abbreviations array is nested
+    const [selectedBuildingsDataPointsSamplingRateAbbrevArr] =
+      selectedBuildingsDataPointsSamplingRateAbbrev;
 
-/**
- * Test drawing of line chart using aggregation / maximum result - daily
- */
-const drawLineChartMonthlyMaxTest = async function () {
-  try {
-    const sensorsOfInterestNestedArr = [
-      ["125", "vl", "60min"],
-      ["225", "vl", "60min"],
-    ];
+    // Extract the formatted sampling rate string - used by ALL chart types
+    const [, , selectedSamplingRateAbbrev] =
+      selectedBuildingsDataPointsSamplingRateAbbrevArr;
 
-    const observationsPlusMetadata =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        sensorsOfInterestNestedArr
-      );
+    // User-specified start date and end date for aggregation - used by MULTIPLE chart types
+    const aggregationStartDate = "2020-01-01";
+    const aggregationEndDate = "2020-12-31";
 
-    // Extract the observations and metadata for each sensor
-    // Array elements in same order as input array
-    const [obsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-    // User-specified start date and end date
-    const startDate = "2020-02-01";
-    const endDate = "2020-12-31";
-
-    // Extract observations within the user-specified start and end date
-    const observationsNestedArr = obsNestedArr.map((obsArr) =>
-      extractObservationsWithinDatesInterval(
-        obsArr,
-        "60min",
-        startDate,
-        endDate
-      )
+    // Extract observations within the user-specified start and end date - used by MULTIPLE chart types
+    const observationsAggregationNestedArr = observationsComboNestedArr.map(
+      (obsArr) =>
+        extractObservationsWithinDatesInterval(
+          obsArr,
+          "60min",
+          aggregationStartDate,
+          aggregationEndDate
+        )
     );
 
-    // Unique calendar dates
-    const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
+    // Unique calendar dates - used by MULTIPLE chart types
+    const uniqueCalendarDatesNestedArr = observationsAggregationNestedArr.map(
       (observationsArr) =>
         extractUniqueCalendarDatesFromTimestamp(observationsArr)
     );
 
-    // Unique calendar months
-    const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
-      (uniqueCalendarDatesArr) =>
-        extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
-    );
-
-    // Calculate maximum values of observations - monthly
-    const observationsMaxMonthlyNestedArr =
-      calculateMaximumObservationValuesWithinInterval(
-        observationsNestedArr,
-        "60min",
-        uniqueCalendarMonthsNestedArr,
-        "monthly"
-      );
-
-    // Format the observations - monthly
-    const formattedObservationsMaxMonthlyNestedArr =
-      observationsMaxMonthlyNestedArr.map((obsMaxMonthlyArr, i) =>
-        formatAggregationResultForColumnChart(
-          uniqueCalendarMonthsNestedArr[i],
-          obsMaxMonthlyArr
-        )
-      );
-
-    // Format the metadata
-    const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
-    );
-
-    // Extract the formatted metadata properties
-    const extractedFormattedDatastreamProperties =
-      extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataNestedArr,
-        true,
-        "monthly",
-        "maximum"
-      );
-
-    drawLineChartHighcharts(
-      formattedObservationsMaxMonthlyNestedArr,
-      extractedFormattedDatastreamProperties
-    );
+    selectedChartTypeArr.forEach((selectedChartType) => {
+      if (selectedChartType === "Heatmap") {
+        // We are interested in raw observations
+        if (
+          checkIfChartRequiresRawObservations(
+            selectedAggregationTypeArr,
+            selectedAggregationDuration
+          )
+        ) {
+          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
+          )
+        ) {
+          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)") {
+          // Create formatted array(s) for observations
+          const formattedRawObservationsLineChartNestedArr =
+            observationsComboNestedArr.map((observationsArr) =>
+              formatSensorThingsApiResponseForLineOrColumnChart(observationsArr)
+            );
+
+          drawLineChartHighcharts(
+            formattedRawObservationsLineChartNestedArr,
+            extractedFormattedDatastreamProperties
+          );
+        }
+        // Aggregated observations
+        else {
+          drawLineChartBasedOnSelectedAggregationOptions(
+            selectedAggregationTypeArr,
+            selectedAggregationDuration,
+            observationsAggregationNestedArr,
+            selectedSamplingRateAbbrev,
+            uniqueCalendarDatesNestedArr,
+            formattedMetadataNestedArr
+          );
+        }
+      }
+      if (selectedChartType === "Column") {
+        // We are interested in raw observations or aggregated observations
+
+        // Raw observations
+        if (selectedAggregationTypeArr === "None (raw data)") {
+          // Create formatted array(s) for observations
+          const formattedRawObservationsColumnChartNestedArr =
+            observationsComboNestedArr.map((observationsArr) =>
+              formatSensorThingsApiResponseForLineOrColumnChart(observationsArr)
+            );
+
+          drawColumnChartHighcharts(
+            formattedRawObservationsColumnChartNestedArr,
+            extractedFormattedDatastreamProperties
+          );
+        }
+        // Aggregated observations
+        else {
+          drawColumnChartBasedOnSelectedAggregationOptions(
+            selectedAggregationTypeArr,
+            selectedAggregationDuration,
+            observationsAggregationNestedArr,
+            selectedSamplingRateAbbrev,
+            uniqueCalendarDatesNestedArr,
+            formattedMetadataNestedArr
+          );
+        }
+      }
+    });
   } catch (err) {
     console.error(err);
+  } finally {
+    // Hide the loading indicator
+    hideLoadingSpinner();
   }
 };
 
-// drawScatterPlotHCTest2();
-// drawHeatmapHCUsingTempDifference();
-// testLineChartMultipleSeries();
-// drawColumnChartMonthlySumTest();
-// drawColumnChartDailySumTest();
-// drawColumnChartNonAggregationTest();
-// drawLineChartMonthlyAverageTest();
-// drawLineChartDailyAverageTest();
-// drawLineChartDailyMinTest();
-// drawLineChartMonthlyMaxTest();
+document.addEventListener("DOMContentLoaded", afterDocumentLoads);
+
+document
+  .querySelector("#btn-draw-chart")
+  .addEventListener("click", drawChartUsingSelectedOptions);
diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js
deleted file mode 100644
index b3526dec37b3f890434f087c7ff1103f3d03414c..0000000000000000000000000000000000000000
--- a/public/js/dropDownList.js
+++ /dev/null
@@ -1,417 +0,0 @@
-"use strict";
-
-import {
-  BASE_URL,
-  QUERY_PARAMS_COMBINED,
-} from "./src_modules/baseUrlPlusQueryParams.mjs";
-
-import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs";
-
-import {
-  formatDatastreamMetadataForChart,
-  extractPropertiesFromFormattedDatastreamMetadata,
-} from "./src_modules/fetchedDataProcessing.mjs";
-
-import {
-  formatSensorThingsApiResponseForLineOrColumnChart,
-  drawLineChartHighcharts,
-} from "./src_modules/chartLine.mjs";
-
-import {
-  formatSensorThingsApiResponseForHeatMap,
-  drawHeatMapHighcharts,
-} from "./src_modules/chartHeatmap.mjs";
-
-import {
-  showLoadingSpinner,
-  hideLoadingSpinner,
-} from "./src_modules/loadingIndicator.mjs";
-
-import { vanillaSelectBox } from "./thirdparty/vanillaSelectBox.mjs";
-
-/**
- * Use the `vanillaDropDown` library to style the first level drop down list
- *
- * @returns {undefined}
- */
-const styleLevelOneDropDown = function () {
-  // Create our dropdown list using `vanillaSelectBox`; supports the selection of multiple options
-  new vanillaSelectBox("#drop-down--bldg-data-point", {
-    "disableSelectAll": true,
-    "maxSelect": 5,
-    "placeHolder": "--Select--",
-    "search": false,
-  });
-};
-
-/**
- * Use the `vanillaDropDown` library to style the second level drop down list
- *
- * @returns {undefined}
- */
-const styleLevelTwoDropDown = function () {
-  // Create our dropdown list using `vanillaSelectBox`
-  new vanillaSelectBox("#drop-down--aggregation-type", {
-    "disableSelectAll": true,
-    "maxSelect": 1,
-    "placeHolder": "--Select--",
-    "search": false,
-  });
-};
-
-/**
- * Use the `vanillaDropDown` library to style the third level drop down list
- *
- * @returns {undefined}
- */
-const styleLevelThreeDropDown = function () {
-  // Create our dropdown list using `vanillaSelectBox`
-  new vanillaSelectBox("#drop-down--sampling-rate", {
-    "disableSelectAll": true,
-    "maxSelect": 1,
-    "placeHolder": "--Select--",
-    "search": false,
-  });
-};
-
-/**
- * Use the `vanillaDropDown` library to style the fourth level drop down list
- *
- * @returns {undefined}
- */
-const styleLevelFourDropDown = function () {
-  // Create our dropdown list using `vanillaSelectBox`
-  new vanillaSelectBox("#drop-down--chart-type", {
-    "disableSelectAll": true,
-    "maxSelect": 1,
-    "placeHolder": "--Select--",
-    "search": false,
-  });
-};
-
-/**
- * Callback function that wraps the logic of populating the linked drop down lists.
- * Will run on `DOMContentLoaded` event
- *
- * @returns {undefined}
- */
-const afterDocumentLoads = function () {
-  styleLevelOneDropDown();
-  styleLevelTwoDropDown();
-  styleLevelThreeDropDown();
-  styleLevelFourDropDown();
-};
-
-/**
- * Get the selected option(s) from a dropdown list
- *
- * @param {String} selectorStr A CSS selector string representing the dropdown list
- * @returns {Array} An array of string(s) representing the value(s) of the selected `<option>` elements
- */
-const getSelectedOptionsFromDropDownList = function (selectorStr) {
-  // Array to store our final result
-  const selectedOptionsArr = [];
-
-  // Select all the matching <option> elements as a NodeList
-  const optionElements = document.querySelectorAll(`${selectorStr} option`);
-
-  optionElements.forEach((optionEl) => {
-    if (optionEl.selected) {
-      selectedOptionsArr.push(optionEl.value);
-    }
-  });
-
-  return selectedOptionsArr;
-};
-
-/**
- * Process the selected option(s) from a buildings & data points dropdown list.
- * This is currently the first dropdown list in the UI.
- *
- * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements
- * @returns {Array} An array of string(s) representing the processed value(s) of the selected buildings & data points option(s)
- */
-const processSelectionsFromBuildingDataPointOptions = function (
-  selectedOptionsArr
-) {
-  // Array to store our final result
-  const selectedOptionsBuildingDataPointArr = [];
-
-  selectedOptionsArr.forEach((optionStr) => {
-    if (optionStr.includes("/")) {
-      // Case 1: <option> element's value CONTAINS a "/" character
-      // We wish to create a string like this `Bau 101/VL`
-
-      // Split the <option> element's value into two substrings
-      const optionsStrPartOne = optionStr.slice(0, 3);
-      const optionsStrPartTwo = optionStr.slice(3);
-
-      // Create a new string for the first substring
-      const optionsStrPartOneNew = `Bau ${optionsStrPartOne}`;
-
-      // Create a new combined string
-      const optionsStrNew = optionsStrPartOneNew + optionsStrPartTwo;
-
-      selectedOptionsBuildingDataPointArr.push(optionsStrNew);
-    } else {
-      // Case 2: <option> element's value DOES NOT CONTAIN a "/" character
-      // We wish to create a string like this `Other/Außentemp`
-      selectedOptionsBuildingDataPointArr.push(`Other/${optionStr}`);
-    }
-  });
-
-  return selectedOptionsBuildingDataPointArr;
-};
-
-/**
- * Split an option element's value (a string) using a forward slash character ("/") as the delimiter
- *
- * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements
- * @returns {Array} An array made up of resulting strings after splitting
- */
-const splitOptionsTextDelimitedBySlash = function (selectedOptionsArr) {
-  return selectedOptionsArr.map((selectedOption) => selectedOption.split("/"));
-};
-
-/**
- * Get the values from the currently selected options in ALL the drop down lists
- * @returns {Array} An array containing four arrays, where each array contains the values of the selected options
- */
-const getSelectedOptionsFromAllDropDownLists = function () {
-  const selectedBuildingDataPointOptionsArr =
-    processSelectionsFromBuildingDataPointOptions(
-      getSelectedOptionsFromDropDownList("#drop-down--bldg-data-point")
-    );
-
-  // Separate the building ID from the data point
-  const selectedBuildingDataPointOptionsSplitArr =
-    splitOptionsTextDelimitedBySlash(selectedBuildingDataPointOptionsArr);
-
-  const selectedAggregationOptionsArr = getSelectedOptionsFromDropDownList(
-    "#drop-down--aggregation-type"
-  );
-
-  const selectedSamplingRateArr = getSelectedOptionsFromDropDownList(
-    "#drop-down--sampling-rate"
-  );
-
-  const selectedChartTypeArr = getSelectedOptionsFromDropDownList(
-    "#drop-down--chart-type"
-  );
-
-  // Ensure that all the options have at least one selection
-
-  if (
-    selectedBuildingDataPointOptionsSplitArr.length === 0 ||
-    selectedAggregationOptionsArr.length === 0 ||
-    selectedSamplingRateArr.length === 0 ||
-    selectedChartTypeArr.length === 0
-  )
-    return;
-
-  return [
-    selectedBuildingDataPointOptionsSplitArr,
-    selectedAggregationOptionsArr,
-    selectedSamplingRateArr,
-    selectedChartTypeArr,
-  ];
-};
-
-/**
- * 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
- * @param {String} phenomenonFullForm A string representation of the full form of a phenomenon name
- * @param {String} samplingRateFullForm A string representation of the full form of a sensor's sampling rate
- * @returns {Array} An array of abbreviated strings
- */
-const getBuildingSensorSamplingRateAbbreviation = function (
-  buildingFullForm,
-  phenomenonFullForm,
-  samplingRateFullForm
-) {
-  const fullFormToAbbreviationMapping = {
-    buildings: {
-      "Bau 101": "101",
-      "Bau 102": "102",
-      "Bau 107": "107",
-      "Bau 112": "112, 118",
-      "Bau 125": "125",
-      "Bau 225": "225",
-      "Other": "weather_station_521",
-    },
-    phenomenon: {
-      VL: "vl",
-      RL: "rl",
-      dT: "dT",
-
-      Durchfluss: "flow",
-      Leistung: "power",
-      Energie: "energy",
-      Energie_VERBR: "energy_verb",
-      Außentemp: "outside_temp",
-    },
-    samplingRate: {
-      "15 min": "15min",
-      "60 min": "60min",
-    },
-  };
-
-  if (
-    fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm] === undefined
-  )
-    throw new Error(
-      "The provided building ID is not valid or is not supported by this function"
-    );
-
-  if (
-    fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm] ===
-    undefined
-  )
-    throw new Error(
-      "The provided data point is not valid or is not supported by this function"
-    );
-
-  if (
-    fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm] ===
-    undefined
-  )
-    throw new Error(
-      "The provided sampling rate is not valid or is not supported by this function"
-    );
-
-  const buildingAbbrev =
-    fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm];
-
-  const phenomenonAbbrev =
-    fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm];
-
-  const samplingRateAbbrev =
-    fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm];
-
-  return [buildingAbbrev, phenomenonAbbrev, samplingRateAbbrev];
-};
-
-/**
- * Get the abbreviated form for the currently selected options in ALL the drop down lists
- *
- * @param {Array} allSelectedOptionsArr An array containing four arrays, where each array contains the values of the selected options
- * @returns {Array} An array which contains one or more nested arrays of abbreviations of building(s), data point(s) and sampling rate(s)
- */
-const getAbbreviationsForSelectedOptionsFromAllDropDownLists = function (
-  allSelectedOptionsArr
-) {
-  // Note: The sampling rate array is the third array, therefore we skip one element
-  const [selectedBuildingDataPointOptionsSplitArr, , selectedSamplingRateArr] =
-    allSelectedOptionsArr;
-
-  // The building is the first element
-  const selectedBuildingsArr = selectedBuildingDataPointOptionsSplitArr.map(
-    (selectedBuildingDataPoint) => selectedBuildingDataPoint[0]
-  );
-
-  // The data point is the second element
-  const selectedDataPointsArr = selectedBuildingDataPointOptionsSplitArr.map(
-    (selectedBuildingDataPoint) => selectedBuildingDataPoint[1]
-  );
-
-  // Assume that the buildings and data points arrays have equal length
-  // use one of the arrays for looping
-  if (selectedBuildingsArr.length !== selectedDataPointsArr.length)
-    throw new Error(
-      "The buildings array and data points array have different lengths"
-    );
-
-  return selectedBuildingsArr.map((selectedBuilding, i) =>
-    getBuildingSensorSamplingRateAbbreviation(
-      selectedBuilding,
-      selectedDataPointsArr[i],
-      ...selectedSamplingRateArr
-    )
-  );
-};
-
-/**
- * Callback function for chart selection using drop down list
- * @returns {undefined}
- */
-const selectChartTypeFromDropDown = async function () {
-  try {
-    const selectedOptions = getSelectedOptionsFromDropDownLists();
-
-    if (selectedOptions === undefined) return;
-
-    const selectedOptionsAbbreviationsArr =
-      getBuildingSensorSamplingRateAbbreviation(...selectedOptions);
-
-    const selectedChartType = document.querySelector(
-      "#drop-down--chart-type"
-    ).value;
-
-    if (selectedChartType === "--Select--") return;
-
-    // Display the loading indicator
-    showLoadingSpinner();
-
-    // The `getMetadataPlusObservationsFromSingleOrMultipleDatastreams` function expects a nested array structure
-    const abbreviationsNestedArr = [selectedOptionsAbbreviationsArr];
-
-    const observationsPlusMetadata =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        BASE_URL,
-        QUERY_PARAMS_COMBINED,
-        abbreviationsNestedArr
-      );
-
-    // Extract the combined arrays for observations and metadata
-    const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-    // Create formatted array(s) for observations - line chart
-    const formattedObsLineChartNestedArr = observationsNestedArr.map(
-      (observationsArr) =>
-        formatSensorThingsApiResponseForLineOrColumnChart(observationsArr)
-    );
-
-    // Create formatted array(s) for observations - heatmap
-    const formattedObsHeatMapNestedArr = observationsNestedArr.map(
-      (observationsArr) =>
-        formatSensorThingsApiResponseForHeatMap(observationsArr)
-    );
-
-    // Create formatted array(s) for metadata - same for both chart types
-    const formattedMetadataArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj)
-    );
-
-    // Extract the formatted metadata properties
-    const extractedFormattedDatastreamProperties =
-      extractPropertiesFromFormattedDatastreamMetadata(
-        formattedMetadataArr,
-        false
-      );
-
-    if (selectedChartType === "Line") {
-      drawLineChartHighcharts(
-        formattedObsLineChartNestedArr,
-        extractedFormattedDatastreamProperties
-      );
-    } else if (selectedChartType === "Heatmap") {
-      // First need to extract the formatted observations from the nested array
-      // Heatmap only needs one set of formatted observation values
-      drawHeatMapHighcharts(
-        ...formattedObsHeatMapNestedArr,
-        extractedFormattedDatastreamProperties
-      );
-    }
-  } catch (err) {
-    console.error(err);
-  } finally {
-    // Hide the loading indicator
-    hideLoadingSpinner();
-  }
-};
-
-document.addEventListener("DOMContentLoaded", afterDocumentLoads);
-
-document
-  .querySelector("#drop-down--chart-type")
-  .addEventListener("change", selectChartTypeFromDropDown);
diff --git a/public/js/src_modules/calculateTemperatureDiff.mjs b/public/js/src_modules/calculateTemperatureDiff.mjs
index b429303342217684dabf1b0ca3126ad1b5b189e8..5b7c55097489f9e000ca603efb4faafe68eb4dee 100644
--- a/public/js/src_modules/calculateTemperatureDiff.mjs
+++ b/public/js/src_modules/calculateTemperatureDiff.mjs
@@ -7,127 +7,139 @@ import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./fe
 import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcessing.mjs";
 
 /**
- * Calculate the temperature difference, dT, between Vorlauf temperature [VL] and Rücklauf temperature [RL] (i.e., dT = VL - RL)
+ * Calculate the temperature difference, dT, between Vorlauf temperature [VL] and
+ * Rücklauf temperature [RL] (i.e., dT = VL - RL). In addition, create synthetic metadata
+ * for the temperature difference
  * @async
  * @param {String} baseUrl Base URL of the STA server
  * @param {Object} urlParams The URL parameters to be sent together with the GET request
- * @param {String} buildingId The building ID as a string
- * @param {String} samplingRate The sampling rate as a string
- * @returns {Promise} A promise that contains an array (that is made up of a temperature difference array and a metadata object) when fulfilled
+ * @param {Array} buildingSamplingRateNestedArr A N*1 array (where N >= 1) containing a nested array of buildings & sampling rates as strings, i.e. [["101", "15min"]] or [["101", "15min"], ["102", "60min"]] or [["101", "15min"], ["102", "60min"], ["225", "60min"]], etc
+ * @returns {Promise} A promise that contains a 1*2 array (the first element is an array that contans N Observations arrays; and the second element is an array of N Datastream metadata objects) when fulfilled
  */
 export const calculateVorlaufMinusRuecklaufTemperature = async function (
   baseUrl,
   urlParams,
-  buildingId,
-  samplingRate
+  buildingSamplingRateNestedArr
 ) {
   try {
-    const bldgSensorSamplingRateNestedArr = [
-      [buildingId, "vl", samplingRate],
-      [buildingId, "rl", samplingRate],
-    ];
-
-    const BUILDING_ID = buildingId;
-    const SAMPLING_RATE = samplingRate;
-
-    const observationsPlusMetadata =
-      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-        baseUrl,
-        urlParams,
-        bldgSensorSamplingRateNestedArr
+    // Arrays to store our results
+    const combinedObservationsArr = [];
+    const combinedMetadataArr = [];
+
+    const buildingDataPointSamplingRateNestedTwiceArr =
+      buildingSamplingRateNestedArr.map((bldgSmplngRate) => {
+        // The building ID is the first element, sampling rate is second element
+        return [
+          [bldgSmplngRate[0], "vl", bldgSmplngRate[1]],
+          [bldgSmplngRate[0], "rl", bldgSmplngRate[1]],
+        ];
+      });
+
+    // Note: We have to use a for/of loop here due to the asynchronous nature of our code
+    for (const bldgDataPtSamplingRateNestedArr of buildingDataPointSamplingRateNestedTwiceArr) {
+      // Use the first element of the nested array to extract building ID + sampling rate
+      // Note: we skip the second element
+      const [buildingId, , samplingRate] = bldgDataPtSamplingRateNestedArr[0];
+
+      const BUILDING_ID = buildingId;
+      const SAMPLING_RATE = samplingRate;
+
+      const observationsPlusMetadata =
+        await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
+          baseUrl,
+          urlParams,
+          bldgDataPtSamplingRateNestedArr
+        );
+
+      // Extract Vorlauf temperature, Ruecklauf temperature and metadata
+      const [
+        [vorlaufTemperatureObsArr, ruecklaufTemperatureObsArr],
+        [metadataVorlauf, metadataRuecklauf],
+      ] = observationsPlusMetadata;
+
+      // Compare the lengths of the observations arrays for VL and RL,
+      // delete the unique observation(s), if necessary
+      const [vorlaufTemperatureObsFinalArr, ruecklaufTemperatureObsFinalArr] =
+        vorlaufTemperatureObsArr.length === ruecklaufTemperatureObsArr.length
+          ? [vorlaufTemperatureObsArr, ruecklaufTemperatureObsArr]
+          : checkForAndDeleteUniqueObservationsFromLargerArray(
+              vorlaufTemperatureObsArr,
+              ruecklaufTemperatureObsArr
+            );
+
+      // Extract the temperature values
+      const vorlaufTemperatureValues = vorlaufTemperatureObsFinalArr.map(
+        (vlTempObs) => vlTempObs[1]
+      );
+      const ruecklaufTemperatureValues = ruecklaufTemperatureObsFinalArr.map(
+        (rlTempObs) => rlTempObs[1]
       );
 
-    // Extract Vorlauf temperature, Ruecklauf temperature and metadata
-    const [
-      [vorlaufTemperatureObsArr, ruecklaufTemperatureObsArr],
-      [metadataVorlauf, metadataRuecklauf],
-    ] = observationsPlusMetadata;
-
-    // Compare the lengths of the observations arrays for VL and RL,
-    // delete the unique observation(s), if necessary
-    const [vorlaufTemperatureObsFinalArr, ruecklaufTemperatureObsFinalArr] =
-      vorlaufTemperatureObsArr.length === ruecklaufTemperatureObsArr.length
-        ? [vorlaufTemperatureObsArr, ruecklaufTemperatureObsArr]
-        : checkForAndDeleteUniqueObservationsFromLargerArray(
-            vorlaufTemperatureObsArr,
-            ruecklaufTemperatureObsArr
-          );
-
-    // Extract the temperature values
-    const vorlaufTemperatureValues = vorlaufTemperatureObsFinalArr.map(
-      (vlTempObs) => vlTempObs[1]
-    );
-    const ruecklaufTemperatureValues = ruecklaufTemperatureObsFinalArr.map(
-      (rlTempObs) => rlTempObs[1]
-    );
-
-    // The arrays have equal length, we need only use one of them for looping
-    // Resulting array contains the following pairs (timestamp + dT)
-    const vorlaufMinusRuecklaufTemperatureObs = vorlaufTemperatureObsArr.map(
-      (vlTempObs, i) => {
-        // Use timestamp from VL, since is equal to that of RL
-        const timestamp = vlTempObs[0];
-
-        // Case 1: One of the observation values is `null`,
-        // no need to calculate temperature difference
-        if (
-          vorlaufTemperatureValues[i] === null ||
-          ruecklaufTemperatureValues[i] === null
-        ) {
-          return [timestamp, null];
+      // The arrays have equal length, we need only use one of them for looping
+      // Resulting array contains the following pairs (timestamp + dT)
+      const vorlaufMinusRuecklaufTemperatureObs = vorlaufTemperatureObsArr.map(
+        (vlTempObs, i) => {
+          // Use timestamp from VL, since is equal to that of RL
+          const timestamp = vlTempObs[0];
+
+          // Case 1: One of the observation values is `null`,
+          // no need to calculate temperature difference
+          if (
+            vorlaufTemperatureValues[i] === null ||
+            ruecklaufTemperatureValues[i] === null
+          ) {
+            return [timestamp, null];
+          }
+
+          // Case 2: Neither of the observation values is `null`,
+          // calculate temperature difference
+          return [
+            timestamp,
+            vorlaufTemperatureValues[i] - ruecklaufTemperatureValues[i],
+          ];
         }
+      );
 
-        // Case 2: Neither of the observation values is `null`,
-        // calculate temperature difference
-        return [
-          timestamp,
-          vorlaufTemperatureValues[i] - ruecklaufTemperatureValues[i],
-        ];
-      }
-    );
-
-    // From Vorlauf metadata, extract `name` and `unitOfMeasurement`
-    const {
-      name: datastreamNameVorlauf,
-      unitOfMeasurement: unitOfMeasurementVorlauf,
-    } = metadataVorlauf;
-
-    // From Ruecklauf metadata, extract `name`
-    const { name: datastreamNameRuecklauf } = metadataRuecklauf;
-
-    // Extract the phenomenon names from the Datastream names
-    const phenomenonNameVorlauf = extractPhenomenonNameFromDatastreamName(
-      datastreamNameVorlauf
-    );
-    const phenomenonNameRuecklauf = extractPhenomenonNameFromDatastreamName(
-      datastreamNameRuecklauf
-    );
-
-    // Create our custom datastream description text
-    // The resulting datastream description string has two `temperature` substrings;
-    // replace the first occurence with an empty string
-    const descriptionTempDifference =
-      `Computed dT: ${phenomenonNameVorlauf} minus ${phenomenonNameRuecklauf}`.replace(
-        "temperature",
-        ""
+      // From Vorlauf metadata, extract `name` and `unitOfMeasurement`
+      const {
+        name: datastreamNameVorlauf,
+        unitOfMeasurement: unitOfMeasurementVorlauf,
+      } = metadataVorlauf;
+
+      // From Ruecklauf metadata, extract `name`
+      const { name: datastreamNameRuecklauf } = metadataRuecklauf;
+
+      // Extract the phenomenon names from the Datastream names
+      const phenomenonNameVorlauf = extractPhenomenonNameFromDatastreamName(
+        datastreamNameVorlauf
+      );
+      const phenomenonNameRuecklauf = extractPhenomenonNameFromDatastreamName(
+        datastreamNameRuecklauf
       );
 
-    // Create our custom datastream name text
-    const nameTempDifference = `BOSCH_${BUILDING_ID} / dT Temperature difference (VL-RL) DS:${SAMPLING_RATE}`;
-
-    // The datastream object that we return needs to have these property names
-    const description = descriptionTempDifference;
-    const name = nameTempDifference;
-    const unitOfMeasurement = unitOfMeasurementVorlauf;
-
-    return [
-      vorlaufMinusRuecklaufTemperatureObs,
-      {
-        description,
-        name,
-        unitOfMeasurement,
-      },
-    ];
+      // Create our custom datastream description text
+      // The resulting datastream description string has two `temperature` substrings;
+      // replace the first occurence with an empty string
+      const descriptionTempDifference =
+        `Computed dT: ${phenomenonNameVorlauf} minus ${phenomenonNameRuecklauf}`.replace(
+          "temperature",
+          ""
+        );
+
+      // Create our custom datastream name text
+      const nameTempDifference = `BOSCH_${BUILDING_ID} / dT Temperature difference (VL-RL) DS:${SAMPLING_RATE}`;
+
+      // The datastream object that we return needs to have these property names
+      const description = descriptionTempDifference;
+      const name = nameTempDifference;
+      const unitOfMeasurement = unitOfMeasurementVorlauf;
+
+      // Add the observations and metadata to our arrays
+      combinedObservationsArr.push(vorlaufMinusRuecklaufTemperatureObs);
+      combinedMetadataArr.push({ description, name, unitOfMeasurement });
+    }
+
+    return [combinedObservationsArr, combinedMetadataArr];
   } catch (err) {
     console.error(err);
   }
diff --git a/public/js/src_modules/dropDownListHelpers.mjs b/public/js/src_modules/dropDownListHelpers.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..d70d7143c7436127589cf13008e75666b18783e3
--- /dev/null
+++ b/public/js/src_modules/dropDownListHelpers.mjs
@@ -0,0 +1,422 @@
+/**
+ * Get the selected option(s) from a dropdown list
+ *
+ * @param {String} selectorStr A CSS selector string representing the dropdown list
+ * @returns {Array} An array of string(s) representing the value(s) of the selected `<option>` elements
+ */
+const getSelectedOptionsFromDropDownList = function (selectorStr) {
+  // Array to store our final result
+  const selectedOptionsArr = [];
+
+  // Select all the matching <option> elements as a NodeList
+  const optionElements = document.querySelectorAll(`${selectorStr} option`);
+
+  optionElements.forEach((optionEl) => {
+    if (optionEl.selected) {
+      selectedOptionsArr.push(optionEl.value);
+    }
+  });
+
+  return selectedOptionsArr;
+};
+
+/**
+ * Process the selected option(s) from a buildings & data points dropdown list.
+ *
+ * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements
+ * @returns {Array} An array of string(s) representing the processed value(s) of the selected buildings & data points option(s)
+ */
+const processSelectionsFromBuildingDataPointOptions = function (
+  selectedOptionsArr
+) {
+  // Array to store our final result
+  const selectedOptionsBuildingDataPointArr = [];
+
+  selectedOptionsArr.forEach((optionStr) => {
+    // Case 1: <option> element's value CONTAINS a "/" character
+    // We wish to create a string like this `Bau 101/VL`
+    if (optionStr.includes("/")) {
+      // Split the <option> element's value into two substrings
+      const optionsStrPartOne = optionStr.slice(0, 3);
+      const optionsStrPartTwo = optionStr.slice(3);
+
+      // Create a new string for the first substring
+      const optionsStrPartOneNew = `Bau ${optionsStrPartOne}`;
+
+      // Create a new combined string
+      const optionsStrNew = optionsStrPartOneNew + optionsStrPartTwo;
+
+      selectedOptionsBuildingDataPointArr.push(optionsStrNew);
+    }
+    // Case 2: <option> element's value DOES NOT CONTAIN a "/" character
+    // We wish to create a string like this `Other/Außentemp`
+    else {
+      selectedOptionsBuildingDataPointArr.push(`Other/${optionStr}`);
+    }
+  });
+
+  return selectedOptionsBuildingDataPointArr;
+};
+
+/**
+ * Split an option element's value (a string) using a forward slash character ("/") as the delimiter
+ *
+ * @param {String} selectedOptionsStr A string representing the value of the selected `<option>` element
+ * @returns {String} Resulting strings after splitting
+ */
+const splitOptionsTextDelimitedBySlash = function (selectedOptionsStr) {
+  return selectedOptionsStr.split("/");
+};
+
+/**
+ * Split an array of option element's values (strings) which have a forward slash character ("/") as the delimiter
+ *
+ * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements
+ * @returns {Array} An array made up of resulting strings after splitting
+ */
+const splitMultipleOptionsTextDelimitedBySlash = function (selectedOptionsArr) {
+  return selectedOptionsArr.map((selectedOption) =>
+    splitOptionsTextDelimitedBySlash(selectedOption)
+  );
+};
+
+/**
+ * Get the values from the currently selected options in ALL the drop down lists
+ * @returns {Array} An array containing four arrays, where each array contains the values of the selected options
+ */
+const getSelectedOptionsFromAllDropDownLists = function () {
+  const selectedBuildingDataPointOptionsArr =
+    processSelectionsFromBuildingDataPointOptions(
+      getSelectedOptionsFromDropDownList("#drop-down--bldg-data-point")
+    );
+
+  // Separate the building ID from the data point
+  const selectedBuildingDataPointOptionsSplitArr =
+    splitMultipleOptionsTextDelimitedBySlash(
+      selectedBuildingDataPointOptionsArr
+    );
+
+  const selectedAggregationOptionsArr = getSelectedOptionsFromDropDownList(
+    "#drop-down--aggregation-type"
+  );
+
+  const selectedSamplingRateArr = getSelectedOptionsFromDropDownList(
+    "#drop-down--sampling-rate"
+  );
+
+  const selectedChartTypeArr = getSelectedOptionsFromDropDownList(
+    "#drop-down--chart-type"
+  );
+
+  // Ensure that all the options have at least one selection
+  if (
+    selectedBuildingDataPointOptionsSplitArr.length === 0 ||
+    selectedAggregationOptionsArr.length === 0 ||
+    selectedSamplingRateArr.length === 0 ||
+    selectedChartTypeArr.length === 0
+  )
+    return;
+
+  return [
+    selectedBuildingDataPointOptionsSplitArr,
+    selectedAggregationOptionsArr,
+    selectedSamplingRateArr,
+    selectedChartTypeArr,
+  ];
+};
+
+/**
+ * Check whether the abbreviated buildings + data points + sampling rate strings
+ * contain the temperature difference [dT] between Vorlauf temperature [VL] and
+ * Rücklauf temperature [RL] (i.e., dT = VL - RL). Unlike all the other data points,
+ * this data point is computed in a separate step
+ *
+ * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of strings representing the abbreviated building + data point + sampling rate values
+ * @returns {Boolean} true if the selected options contain the string `dT`, false otherwise
+ */
+const checkIfSelectedOptionsContainTemperatureDifference = function (
+  buildingDataPointSamplingRateAbbrevArr
+) {
+  // Create a flattened copy of our input array,
+  // then check if it contains the string `dT`
+  return buildingDataPointSamplingRateAbbrevArr.flat().includes("dT");
+};
+
+/**
+ * Get the index(es) of the the abbreviated buildings + data points + sampling rate string(s)
+ * that contains the temperature difference (dT)
+ *
+ * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
+ * @returns {Array} An array that contains an integer(s) whose value(s) are the index(es) of the abbreviated building + data point + sampling rate string(s) containing the temperature difference (dT)
+ */
+const getIndexesOfTemperatureDifferenceOptions = function (
+  buildingDataPointSamplingRateAbbrevArr
+) {
+  // An array to store the final result
+  const foundIndexesArr = [];
+
+  // Use the index, i, provided by `forEach` array method
+  buildingDataPointSamplingRateAbbrevArr.forEach(
+    (bldgDataPntSamplingRateAbbrvArr, i) => {
+      if (bldgDataPntSamplingRateAbbrvArr.includes("dT")) {
+        foundIndexesArr.push(i);
+      }
+    }
+  );
+
+  return foundIndexesArr;
+};
+
+/**
+ * Delete the abbreviated building + data point + sampling rate string(s) that contains the temperature difference (dT)
+ *
+ * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
+ * @returns {Array} An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
+ */
+const deleteTemperatureDifferenceOptions = function (
+  buildingDataPointSamplingRateAbbrevArr
+) {
+  // Calculate the index(es) that we wish to delete
+  const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions(
+    buildingDataPointSamplingRateAbbrevArr
+  );
+
+  // Delete the index(es) of `dT`, modifies the array in place
+  // Note: The resulting array is sparse
+  foundIndexesArr.forEach(
+    (foundIndex) => delete buildingDataPointSamplingRateAbbrevArr[foundIndex]
+  );
+
+  // Array to store our final result
+  const buildingDataPointFinalArr = [];
+
+  // Remove the empty sub array(s) that makes entire array sparse
+  // Note: `empty` does not mean `undefined` or `null`
+  buildingDataPointSamplingRateAbbrevArr.forEach(
+    (bldgDataPntSmplingRateAbbrvArr) => {
+      if (typeof bldgDataPntSmplingRateAbbrvArr === "object") {
+        buildingDataPointFinalArr.push(bldgDataPntSmplingRateAbbrvArr);
+      }
+    }
+  );
+
+  return buildingDataPointFinalArr;
+};
+
+/**
+ * Extract the abbreviated building + data point + sampling rate string(s) that contains the temperature difference (dT)
+ *
+ * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
+ * @returns {Array} An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
+ */
+const extractTemperatureDifferenceOptions = function (
+  buildingDataPointSamplingRateAbbrevArr
+) {
+  // Array to store final result
+  const temperatureDifferenceOptionsAbbrevArr = [];
+
+  // Calculate the index(es) that we wish to extract
+  const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions(
+    buildingDataPointSamplingRateAbbrevArr
+  );
+
+  foundIndexesArr.forEach((foundIndex) => {
+    // Extracted array for a single found index
+    const bldgDataPntSamplingRateAbbrvArr =
+      buildingDataPointSamplingRateAbbrevArr[foundIndex];
+
+    // Extract the building and sampling rate strings
+    // Note: we have ignored the second element
+    // const [bldgAbbrv, , samplingRateAbbrv] = bldgDataPntSamplingRateAbbrvArr;
+
+    // Create a new array that contains two elements,
+    // the building and sampling rate abbreviated strings
+    // const bldgSamplingRateAbbrvArr = [bldgAbbrv, samplingRateAbbrv];
+
+    temperatureDifferenceOptionsAbbrevArr.push(bldgDataPntSamplingRateAbbrvArr);
+  });
+
+  return temperatureDifferenceOptionsAbbrevArr;
+};
+
+/**
+ * Extract the abbreviated building + sampling rate string(s) for use in calculating the temperature difference (dT = VL - RL)
+ *
+ * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
+ * @returns {Array} An array that contains nested array(s) made up of abbreviated building + sampling rate string(s)
+ */
+const extractBuildingPlusSamplingRate = function (
+  buildingDataPointSamplingRateAbbrevArr
+) {
+  // Array to store final result
+  const temperatureDifferenceOptionsAbbrevArr = [];
+
+  // Calculate the index(es) that we wish to extract
+  const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions(
+    buildingDataPointSamplingRateAbbrevArr
+  );
+
+  foundIndexesArr.forEach((foundIndex) => {
+    // Extracted array for a single found index
+    const bldgDataPntSamplingRateAbbrvArr =
+      buildingDataPointSamplingRateAbbrevArr[foundIndex];
+
+    // Extract the building and sampling rate strings
+    // Note: we have ignored the second element
+    const [bldgAbbrv, , samplingRateAbbrv] = bldgDataPntSamplingRateAbbrvArr;
+
+    // Create a new array that contains two elements,
+    // the building and sampling rate abbreviated strings
+    const bldgSamplingRateAbbrvArr = [bldgAbbrv, samplingRateAbbrv];
+
+    temperatureDifferenceOptionsAbbrevArr.push(bldgSamplingRateAbbrvArr);
+  });
+
+  return temperatureDifferenceOptionsAbbrevArr;
+};
+
+/**
+ * Determine if a chart requires raw observations instead of aggregated observations
+ *
+ * @param {String} selectedAggregationType The selected aggregation type
+ * @param {String} selectedAggregationDuration The selected aggregation duration
+ * @returns {Boolean} true if the chart requires raw observations, false if not
+ */
+const checkIfChartRequiresRawObservations = function (
+  selectedAggregationType,
+  selectedAggregationDuration
+) {
+  if (
+    selectedAggregationType === "None (raw data)" &&
+    selectedAggregationDuration === undefined
+  ) {
+    return true;
+  } else {
+    return false;
+  }
+};
+
+/**
+ * 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
+ * @param {String} phenomenonFullForm A string representation of the full form of a phenomenon name
+ * @param {String} samplingRateFullForm A string representation of the full form of a sensor's sampling rate
+ * @returns {Array} An array of abbreviated strings
+ */
+const getBuildingSensorSamplingRateAbbreviation = function (
+  buildingFullForm,
+  phenomenonFullForm,
+  samplingRateFullForm
+) {
+  const fullFormToAbbreviationMapping = {
+    buildings: {
+      "Bau 101": "101",
+      "Bau 102": "102",
+      "Bau 107": "107",
+      "Bau 112": "112, 118",
+      "Bau 125": "125",
+      "Bau 225": "225",
+      "Other": "weather_station_521",
+    },
+
+    phenomenon: {
+      VL: "vl",
+      RL: "rl",
+      dT: "dT",
+      Durchfluss: "flow",
+      Leistung: "power",
+      Energie: "energy",
+      Energie_VERBR: "energy_verb",
+      Außentemp: "outside_temp",
+    },
+
+    samplingRate: {
+      "15 min": "15min",
+      "60 min": "60min",
+    },
+  };
+
+  if (
+    fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm] === undefined
+  )
+    throw new Error(
+      "The provided building ID is not valid or is not supported by this function"
+    );
+
+  if (
+    fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm] ===
+    undefined
+  )
+    throw new Error(
+      "The provided data point is not valid or is not supported by this function"
+    );
+
+  if (
+    fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm] ===
+    undefined
+  )
+    throw new Error(
+      "The provided sampling rate is not valid or is not supported by this function"
+    );
+
+  const buildingAbbrev =
+    fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm];
+
+  const phenomenonAbbrev =
+    fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm];
+
+  const samplingRateAbbrev =
+    fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm];
+
+  return [buildingAbbrev, phenomenonAbbrev, samplingRateAbbrev];
+};
+
+/**
+ * Get the abbreviated form for the currently selected options in ALL the drop down lists
+ *
+ * @param {Array} allSelectedOptionsArr An array containing four arrays, where each array contains the values of the selected options
+ * @returns {Array} An array which contains one or more nested arrays of abbreviations of building(s), data point(s) and sampling rate(s)
+ */
+const getAbbreviationsForSelectedOptionsFromAllDropDownLists = function (
+  allSelectedOptionsArr
+) {
+  // Note: The sampling rate array is the third array, therefore we skip one element
+  const [selectedBuildingDataPointOptionsSplitArr, , selectedSamplingRateArr] =
+    allSelectedOptionsArr;
+
+  // The building is the first element
+  const selectedBuildingsArr = selectedBuildingDataPointOptionsSplitArr.map(
+    (selectedBuildingDataPoint) => selectedBuildingDataPoint[0]
+  );
+
+  // The data point is the second element
+  const selectedDataPointsArr = selectedBuildingDataPointOptionsSplitArr.map(
+    (selectedBuildingDataPoint) => selectedBuildingDataPoint[1]
+  );
+
+  // Assume that the buildings and data points arrays have equal length
+  // use one of the arrays for looping
+  if (selectedBuildingsArr.length !== selectedDataPointsArr.length)
+    throw new Error(
+      "The buildings array and data points array have different lengths"
+    );
+
+  return selectedBuildingsArr.map((selectedBuilding, i) =>
+    getBuildingSensorSamplingRateAbbreviation(
+      selectedBuilding,
+      selectedDataPointsArr[i],
+      ...selectedSamplingRateArr
+    )
+  );
+};
+
+export {
+  splitMultipleOptionsTextDelimitedBySlash,
+  getSelectedOptionsFromAllDropDownLists,
+  checkIfSelectedOptionsContainTemperatureDifference,
+  deleteTemperatureDifferenceOptions,
+  extractTemperatureDifferenceOptions,
+  extractBuildingPlusSamplingRate,
+  checkIfChartRequiresRawObservations,
+  getAbbreviationsForSelectedOptionsFromAllDropDownLists,
+};
diff --git a/public/js/src_modules/dropDownListProcessing.mjs b/public/js/src_modules/dropDownListProcessing.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..28d0eadcf154d53f4702cd5fcd389817267ccd08
--- /dev/null
+++ b/public/js/src_modules/dropDownListProcessing.mjs
@@ -0,0 +1,864 @@
+import { drawLineChartHighcharts } from "./chartLine.mjs";
+
+import {
+  formatSensorThingsApiResponseForHeatMap,
+  drawHeatMapHighcharts,
+} from "./chartHeatmap.mjs";
+
+import {
+  formatSensorThingsApiResponseForScatterPlot,
+  drawScatterPlotHighcharts,
+} from "./chartScatterPlot.mjs";
+
+import {
+  formatAggregationResultForColumnChart,
+  drawColumnChartHighcharts,
+} from "./chartColumn.mjs";
+
+import { extractUniqueCalendarMonthsFromCalendarDates } from "./aggregateHelpers.mjs";
+
+import {
+  calculateMinimumObservationValuesWithinInterval,
+  calculateMaximumObservationValuesWithinInterval,
+  calculateSumOfObservationValuesWithinInterval,
+  calculateAverageOfObservationValuesWithinInterval,
+} from "./aggregate.mjs";
+
+import { extractPropertiesFromFormattedDatastreamMetadata } from "./fetchedDataProcessing.mjs";
+
+/**
+ * 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"
+    );
+  }
+};
+
+/**
+ * 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");
+  }
+};
+
+/**
+ * Calculate the daily sum of observations and format these aggregated observations
+ *
+ * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
+ * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations
+ * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
+ * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
+ * @returns {Array} An array whose first element is the formatted aggregated (daily sum) observations. The second element is an object made up of extracted & formatted datastream properties
+ */
+const calculateAndFormatDailySumObservations = function (
+  uniqueCalendarDatesNestedArr,
+  observationsAggregationNestedArr,
+  selectedSamplingRateAbbrev,
+  formattedMetadataNestedArr
+) {
+  // Calculate sum of values of observations - daily
+  const observationsSumDailyNestedArr =
+    calculateSumOfObservationValuesWithinInterval(
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      uniqueCalendarDatesNestedArr,
+      "daily"
+    );
+
+  // Format the observations - sum / daily
+  const formattedObservationsSumDailyNestedArr =
+    observationsSumDailyNestedArr.map((obsSumDailyArr, i) =>
+      formatAggregationResultForColumnChart(
+        uniqueCalendarDatesNestedArr[i],
+        obsSumDailyArr
+      )
+    );
+
+  // Extract the formatted metadata properties
+  const extractedFormattedDatastreamProperties =
+    extractPropertiesFromFormattedDatastreamMetadata(
+      formattedMetadataNestedArr,
+      true,
+      "daily",
+      "sum"
+    );
+
+  return [
+    formattedObservationsSumDailyNestedArr,
+    extractedFormattedDatastreamProperties,
+  ];
+};
+
+/**
+ * Calculate the monthly sum of observations and format these aggregated observations
+ *
+ * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
+ * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations
+ * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
+ * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
+ * @returns {Array} An array whose first element is the formatted aggregated (monthly sum) observations. The second element is an object made up of extracted & formatted datastream properties
+ */
+const calculateAndFormatMonthlySumObservations = function (
+  uniqueCalendarDatesNestedArr,
+  observationsAggregationNestedArr,
+  selectedSamplingRateAbbrev,
+  formattedMetadataNestedArr
+) {
+  // Unique calendar months
+  const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
+    (uniqueCalendarDatesArr) =>
+      extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
+  );
+
+  // Calculate sum of values of observations - monthly
+  const observationsSumMonthlyNestedArr =
+    calculateSumOfObservationValuesWithinInterval(
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      uniqueCalendarMonthsNestedArr,
+      "monthly"
+    );
+
+  // Format the observations - sum / monthly
+  const formattedObservationsSumMonthlyNestedArr =
+    observationsSumMonthlyNestedArr.map((obsSumMonthlyArr, i) =>
+      formatAggregationResultForColumnChart(
+        uniqueCalendarMonthsNestedArr[i],
+        obsSumMonthlyArr
+      )
+    );
+
+  // Extract the formatted metadata properties
+  const extractedFormattedDatastreamProperties =
+    extractPropertiesFromFormattedDatastreamMetadata(
+      formattedMetadataNestedArr,
+      true,
+      "monthly",
+      "sum"
+    );
+
+  return [
+    formattedObservationsSumMonthlyNestedArr,
+    extractedFormattedDatastreamProperties,
+  ];
+};
+
+/**
+ * Calculate the daily maximum of observations and format these aggregated observations
+ *
+ * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
+ * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations
+ * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
+ * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
+ * @returns {Array} An array whose first element is the formatted aggregated (daily maximum) observations. The second element is an object made up of extracted & formatted datastream properties
+ */
+const calculateAndFormatDailyMaximumObservations = function (
+  uniqueCalendarDatesNestedArr,
+  observationsAggregationNestedArr,
+  selectedSamplingRateAbbrev,
+  formattedMetadataNestedArr
+) {
+  // Calculate minimum of values of observations - daily
+  const observationsMaximumDailyNestedArr =
+    calculateMaximumObservationValuesWithinInterval(
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      uniqueCalendarDatesNestedArr,
+      "daily"
+    );
+
+  // Format the observations - min / daily
+  const formattedObservationsMaximumDailyNestedArr =
+    observationsMaximumDailyNestedArr.map((obsMinDailyArr, i) =>
+      formatAggregationResultForColumnChart(
+        uniqueCalendarDatesNestedArr[i],
+        obsMinDailyArr
+      )
+    );
+
+  // Extract the formatted metadata properties
+  const extractedFormattedDatastreamProperties =
+    extractPropertiesFromFormattedDatastreamMetadata(
+      formattedMetadataNestedArr,
+      true,
+      "daily",
+      "maximum"
+    );
+
+  return [
+    formattedObservationsMaximumDailyNestedArr,
+    extractedFormattedDatastreamProperties,
+  ];
+};
+
+/**
+ * Calculate the monthly maximum of observations and format these aggregated observations
+ *
+ * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
+ * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations
+ * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
+ * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
+ * @returns {Array} An array whose first element is the formatted aggregated (monthly maximum) observations. The second element is an object made up of extracted & formatted datastream properties
+ */
+const calculateAndFormatMonthlyMaximumObservations = function (
+  uniqueCalendarDatesNestedArr,
+  observationsAggregationNestedArr,
+  selectedSamplingRateAbbrev,
+  formattedMetadataNestedArr
+) {
+  // Unique calendar months
+  const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
+    (uniqueCalendarDatesArr) =>
+      extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
+  );
+
+  // Calculate minimum of values of observations - monthly
+  const observationsMaximumMonthlyNestedArr =
+    calculateMaximumObservationValuesWithinInterval(
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      uniqueCalendarMonthsNestedArr,
+      "monthly"
+    );
+
+  // Format the observations - max / monthly
+  const formattedObservationsMaximumMonthlyNestedArr =
+    observationsMaximumMonthlyNestedArr.map((obsMaxMonthlyArr, i) =>
+      formatAggregationResultForColumnChart(
+        uniqueCalendarMonthsNestedArr[i],
+        obsMaxMonthlyArr
+      )
+    );
+
+  // Extract the formatted metadata properties
+  const extractedFormattedDatastreamProperties =
+    extractPropertiesFromFormattedDatastreamMetadata(
+      formattedMetadataNestedArr,
+      true,
+      "monthly",
+      "maximum"
+    );
+
+  return [
+    formattedObservationsMaximumMonthlyNestedArr,
+    extractedFormattedDatastreamProperties,
+  ];
+};
+
+/**
+ * Calculate the daily minimum of observations and format these aggregated observations
+ *
+ * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
+ * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations
+ * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
+ * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
+ * @returns {Array} An array whose first element is the formatted aggregated (daily minimum) observations. The second element is an object made up of extracted & formatted datastream properties
+ */
+const calculateAndFormatDailyMinimumObservations = function (
+  uniqueCalendarDatesNestedArr,
+  observationsAggregationNestedArr,
+  selectedSamplingRateAbbrev,
+  formattedMetadataNestedArr
+) {
+  // Calculate minimum of values of observations - daily
+  const observationsMinimumDailyNestedArr =
+    calculateMinimumObservationValuesWithinInterval(
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      uniqueCalendarDatesNestedArr,
+      "daily"
+    );
+
+  // Format the observations - min / daily
+  const formattedObservationsMinimumDailyNestedArr =
+    observationsMinimumDailyNestedArr.map((obsMinDailyArr, i) =>
+      formatAggregationResultForColumnChart(
+        uniqueCalendarDatesNestedArr[i],
+        obsMinDailyArr
+      )
+    );
+
+  // Extract the formatted metadata properties
+  const extractedFormattedDatastreamProperties =
+    extractPropertiesFromFormattedDatastreamMetadata(
+      formattedMetadataNestedArr,
+      true,
+      "daily",
+      "minimum"
+    );
+
+  return [
+    formattedObservationsMinimumDailyNestedArr,
+    extractedFormattedDatastreamProperties,
+  ];
+};
+
+/**
+ * Calculate the monthly minimum of observations and format these aggregated observations
+ *
+ * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
+ * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations
+ * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
+ * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
+ * @returns {Array} An array whose first element is the formatted aggregated (monthly minimum) observations. The second element is an object made up of extracted & formatted datastream properties
+ */
+const calculateAndFormatMonthlyMinimumObservations = function (
+  uniqueCalendarDatesNestedArr,
+  observationsAggregationNestedArr,
+  selectedSamplingRateAbbrev,
+  formattedMetadataNestedArr
+) {
+  // Unique calendar months
+  const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
+    (uniqueCalendarDatesArr) =>
+      extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
+  );
+
+  // Calculate minimum of values of observations - monthly
+  const observationsMinimumMonthlyNestedArr =
+    calculateMinimumObservationValuesWithinInterval(
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      uniqueCalendarMonthsNestedArr,
+      "monthly"
+    );
+
+  // Format the observations - min / monthly
+  const formattedObservationsMinimumMonthlyNestedArr =
+    observationsMinimumMonthlyNestedArr.map((obsMinMonthlyArr, i) =>
+      formatAggregationResultForColumnChart(
+        uniqueCalendarMonthsNestedArr[i],
+        obsMinMonthlyArr
+      )
+    );
+
+  // Extract the formatted metadata properties
+  const extractedFormattedDatastreamProperties =
+    extractPropertiesFromFormattedDatastreamMetadata(
+      formattedMetadataNestedArr,
+      true,
+      "monthly",
+      "minimum"
+    );
+
+  return [
+    formattedObservationsMinimumMonthlyNestedArr,
+    extractedFormattedDatastreamProperties,
+  ];
+};
+
+/**
+ * Calculate the daily average of observations and format these aggregated observations
+ *
+ * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
+ * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations
+ * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
+ * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
+ * @returns {Array} An array whose first element is the formatted aggregated (daily average) observations. The second element is an object made up of extracted & formatted datastream properties
+ */
+const calculateAndFormatDailyAverageObservations = function (
+  uniqueCalendarDatesNestedArr,
+  observationsAggregationNestedArr,
+  selectedSamplingRateAbbrev,
+  formattedMetadataNestedArr
+) {
+  // Calculate average of values of observations - daily
+  const observationsAverageDailyNestedArr =
+    calculateAverageOfObservationValuesWithinInterval(
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      uniqueCalendarDatesNestedArr,
+      "daily"
+    );
+
+  // Format the observations - average / daily
+  const formattedObservationsAverageDailyNestedArr =
+    observationsAverageDailyNestedArr.map((obsAverageDailyArr, i) =>
+      formatAggregationResultForColumnChart(
+        uniqueCalendarDatesNestedArr[i],
+        obsAverageDailyArr
+      )
+    );
+
+  // Extract the formatted metadata properties
+  const extractedFormattedDatastreamProperties =
+    extractPropertiesFromFormattedDatastreamMetadata(
+      formattedMetadataNestedArr,
+      true,
+      "daily",
+      "average"
+    );
+
+  return [
+    formattedObservationsAverageDailyNestedArr,
+    extractedFormattedDatastreamProperties,
+  ];
+};
+
+/**
+ * Calculate the monthly average of observations and format these aggregated observations
+ *
+ * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
+ * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations
+ * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
+ * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
+ * @returns {Array} An array whose first element is the formatted aggregated (monthly average) observations. The second element is an object made up of extracted & formatted datastream properties
+ */
+const calculateAndFormatMonthlyAverageObservations = function (
+  uniqueCalendarDatesNestedArr,
+  observationsAggregationNestedArr,
+  selectedSamplingRateAbbrev,
+  formattedMetadataNestedArr
+) {
+  // Unique calendar months
+  const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
+    (uniqueCalendarDatesArr) =>
+      extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
+  );
+
+  // Calculate average of values of observations - monthly
+  const observationsAverageMonthlyNestedArr =
+    calculateAverageOfObservationValuesWithinInterval(
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      uniqueCalendarMonthsNestedArr,
+      "monthly"
+    );
+
+  // Format the observations - average / monthly
+  const formattedObservationsAverageMonthlyNestedArr =
+    observationsAverageMonthlyNestedArr.map((obsAverageMonthlyArr, i) =>
+      formatAggregationResultForColumnChart(
+        uniqueCalendarMonthsNestedArr[i],
+        obsAverageMonthlyArr
+      )
+    );
+
+  // Extract the formatted metadata properties
+  const extractedFormattedDatastreamProperties =
+    extractPropertiesFromFormattedDatastreamMetadata(
+      formattedMetadataNestedArr,
+      true,
+      "monthly",
+      "average"
+    );
+
+  return [
+    formattedObservationsAverageMonthlyNestedArr,
+    extractedFormattedDatastreamProperties,
+  ];
+};
+
+/**
+ * Draw a line chart based on the selected aggregation options from a drop-down list
+ *
+ * @param {String} selectedAggregationType A string representing the selected aggregation type. The currently supported strings include `Sum`, `Maximum`, `Minimum` and `Average`
+ * @param {String} selectedAggregationDuration A string representing the selected aggregation duration. The currently supported strings include `Daily` and `Monthly`
+ * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations
+ * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
+ * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
+ * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
+ * @returns {undefined} undefined
+ */
+const drawLineChartBasedOnSelectedAggregationOptions = function (
+  selectedAggregationType,
+  selectedAggregationDuration,
+  observationsAggregationNestedArr,
+  selectedSamplingRateAbbrev,
+  uniqueCalendarDatesNestedArr,
+  formattedMetadataNestedArr
+) {
+  if (
+    selectedAggregationType === "Sum" &&
+    selectedAggregationDuration === "Daily"
+  ) {
+    // Formatted observations and metadata for chart - daily / sum
+    const [
+      formattedObservationsSumDailyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatDailySumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawLineChartHighcharts(
+      formattedObservationsSumDailyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationType === "Sum" &&
+    selectedAggregationDuration === "Monthly"
+  ) {
+    // Formatted observations and metadata for chart - monthly / sum
+    const [
+      formattedObservationsSumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatMonthlySumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawLineChartHighcharts(
+      formattedObservationsSumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationType === "Maximum" &&
+    selectedAggregationDuration === "Daily"
+  ) {
+    // Formatted observations and metadata for chart - daily / maximum
+    const [
+      formattedObservationsMaximumDailyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatDailyMaximumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawLineChartHighcharts(
+      formattedObservationsMaximumDailyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationType === "Maximum" &&
+    selectedAggregationDuration === "Monthly"
+  ) {
+    // Formatted observations and metadata for chart - monthly / maximum
+    const [
+      formattedObservationsMaximumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatMonthlyMaximumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawLineChartHighcharts(
+      formattedObservationsMaximumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationType === "Minimum" &&
+    selectedAggregationDuration === "Daily"
+  ) {
+    // Formatted observations and metadata for chart - daily / minimum
+    const [
+      formattedObservationsMinimumDailyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatDailyMinimumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawLineChartHighcharts(
+      formattedObservationsMinimumDailyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationType === "Minimum" &&
+    selectedAggregationDuration === "Monthly"
+  ) {
+    // Formatted observations and metadata for chart - monthly / minimum
+    const [
+      formattedObservationsMinimumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatMonthlyMinimumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawLineChartHighcharts(
+      formattedObservationsMinimumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationType === "Average" &&
+    selectedAggregationDuration === "Daily"
+  ) {
+    // Formatted observations and metadata for chart - daily / average
+    const [
+      formattedObservationsAverageDailyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatDailyAverageObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawLineChartHighcharts(
+      formattedObservationsAverageDailyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationType === "Average" &&
+    selectedAggregationDuration === "Monthly"
+  ) {
+    // Formatted observations and metadata for chart - monthly / average
+    const [
+      formattedObservationsAverageMonthlyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatMonthlyAverageObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawLineChartHighcharts(
+      formattedObservationsAverageMonthlyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  }
+};
+
+/**
+ * Draw a column chart based on the selected aggregation options from a drop-down list
+ *
+ * @param {String} selectedAggregationType A string representing the selected aggregation type. The currently supported strings include `Sum`, `Maximum`, `Minimum` and `Average`
+ * @param {String} selectedAggregationDuration A string representing the selected aggregation duration. The currently supported strings include `Daily` and `Monthly`
+ * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations
+ * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
+ * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
+ * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
+ * @returns {undefined} undefined
+ */
+const drawColumnChartBasedOnSelectedAggregationOptions = function (
+  selectedAggregationTypeArr,
+  selectedAggregationDuration,
+  observationsAggregationNestedArr,
+  selectedSamplingRateAbbrev,
+  uniqueCalendarDatesNestedArr,
+  formattedMetadataNestedArr
+) {
+  if (
+    selectedAggregationTypeArr === "Sum" &&
+    selectedAggregationDuration === "Daily"
+  ) {
+    // Formatted observations and metadata for chart - daily / sum
+    const [
+      formattedObservationsSumDailyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatDailySumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawColumnChartHighcharts(
+      formattedObservationsSumDailyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationTypeArr === "Sum" &&
+    selectedAggregationDuration === "Monthly"
+  ) {
+    // Formatted observations and metadata for chart - monthly / sum
+    const [
+      formattedObservationsSumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatMonthlySumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawColumnChartHighcharts(
+      formattedObservationsSumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationTypeArr === "Maximum" &&
+    selectedAggregationDuration === "Daily"
+  ) {
+    // Formatted observations and metadata for chart - daily / maximum
+    const [
+      formattedObservationsMaximumDailyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatDailyMaximumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawColumnChartHighcharts(
+      formattedObservationsMaximumDailyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationTypeArr === "Maximum" &&
+    selectedAggregationDuration === "Monthly"
+  ) {
+    // Formatted observations and metadata for chart - monthly / maximum
+    const [
+      formattedObservationsMaximumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatMonthlyMaximumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawColumnChartHighcharts(
+      formattedObservationsMaximumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationTypeArr === "Minimum" &&
+    selectedAggregationDuration === "Daily"
+  ) {
+    // Formatted observations and metadata for chart - daily / minimum
+    const [
+      formattedObservationsMinimumDailyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatDailyMinimumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawColumnChartHighcharts(
+      formattedObservationsMinimumDailyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationTypeArr === "Minimum" &&
+    selectedAggregationDuration === "Monthly"
+  ) {
+    // Formatted observations and metadata for chart - monthly / minimum
+    const [
+      formattedObservationsMinimumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatMonthlyMinimumObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawColumnChartHighcharts(
+      formattedObservationsMinimumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationTypeArr === "Average" &&
+    selectedAggregationDuration === "Daily"
+  ) {
+    // Formatted observations and metadata for chart - daily / average
+    const [
+      formattedObservationsAverageDailyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatDailyAverageObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawColumnChartHighcharts(
+      formattedObservationsAverageDailyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } else if (
+    selectedAggregationTypeArr === "Average" &&
+    selectedAggregationDuration === "Monthly"
+  ) {
+    // Formatted observations and metadata for chart - monthly / average
+    const [
+      formattedObservationsAverageMonthlyNestedArr,
+      extractedFormattedDatastreamProperties,
+    ] = calculateAndFormatMonthlyAverageObservations(
+      uniqueCalendarDatesNestedArr,
+      observationsAggregationNestedArr,
+      selectedSamplingRateAbbrev,
+      formattedMetadataNestedArr
+    );
+
+    drawColumnChartHighcharts(
+      formattedObservationsAverageMonthlyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  }
+};
+
+export {
+  drawHeatmapBasedOnSelectedOptions,
+  drawScatterPlotFromChartSelection,
+  drawLineChartBasedOnSelectedAggregationOptions,
+  drawColumnChartBasedOnSelectedAggregationOptions,
+};