diff --git a/public/js/appChart.js b/public/js/appChart.js
index a73d8a73d008f6ea7d70b6737ced452265acf7ec..338efe4c53a2cb61a47f6a1c279ccac659c9e914 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -34,9 +34,13 @@ import {
 import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs";
 
 import {
-  calculateSumOfObservationValuesWithinInterval,
+  extractObservationsWithinDatesInterval,
   extractUniqueCalendarDatesFromTimestamp,
   extractUniqueCalendarMonthsFromCalendarDates,
+} from "./src_modules/aggregateHelpers.mjs";
+
+import {
+  calculateSumOfObservationValuesWithinInterval,
   calculateAverageOfObservationValuesWithinInterval,
 } from "./src_modules/aggregate.mjs";
 
@@ -44,388 +48,477 @@ import {
  * Test plotting of temp difference (dT) using heatmap
  */
 const drawHeatmapHCUsingTempDifference = async function () {
-  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,
-  ];
-
-  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
+  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,
+    ];
+
+    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
+      );
+
+    // First need to extract the formatted observations from the nested array
+    // Heatmap only needs one set of formatted observation values
+    drawHeatMapHighcharts(
+      ...formattedTempDiff225NestedArr,
+      extractedFormattedTempDiff225Properties
     );
-
-  // 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);
+  }
 };
 
 /**
  * Test drawing of scatter plot chart
  */
 const drawScatterPlotHCTest2 = async function () {
-  const sensorsOfInterestNestedArr = [
-    ["225", "vl", "60min"],
-    // ["125", "rl", "60min"],
-    ["weather_station_521", "outside_temp", "60min"],
-  ];
-
-  const observationsPlusMetadata =
-    await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-      BASE_URL,
-      QUERY_PARAMS_COMBINED,
-      sensorsOfInterestNestedArr
+  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)
     );
 
-  // Extract the combined arrays for observations and metadata
-  const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-  // Create formatted array(s) for observations
-  // This function expects two arguments, these are unpacked using the spread operator
-  const formattedObservationsArr = formatSensorThingsApiResponseForScatterPlot(
-    ...observationsNestedArr
-  );
+    // Create formatted array(s) for metadata
+    const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
+      formatDatastreamMetadataForChart(metadataObj)
+    );
 
-  // Create formatted array(s) for metadata
-  const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-    formatDatastreamMetadataForChart(metadataObj)
-  );
+    // Extract the formatted metadata properties
+    const extractedFormattedDatastreamProperties =
+      extractPropertiesFromFormattedDatastreamMetadata(
+        formattedMetadataNestedArr
+      );
 
-  // Extract the formatted metadata properties
-  const extractedFormattedDatastreamProperties =
-    extractPropertiesFromFormattedDatastreamMetadata(
-      formattedMetadataNestedArr
+    drawScatterPlotHighcharts(
+      formattedObservationsArr,
+      extractedFormattedDatastreamProperties
     );
-
-  drawScatterPlotHighcharts(
-    formattedObservationsArr,
-    extractedFormattedDatastreamProperties
-  );
+  } catch (err) {
+    console.error(err);
+  }
 };
 
 /**
  * Test drawing of line chart with multiple series
  */
 const testLineChartMultipleSeries = async function () {
-  const sensorsOfInterestNestedArr = [
-    ["225", "vl", "60min"],
-    ["125", "rl", "60min"],
-    ["weather_station_521", "outside_temp", "60min"],
-  ];
-
-  const observationsPlusMetadataArr =
-    await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-      BASE_URL,
-      QUERY_PARAMS_COMBINED,
-      sensorsOfInterestNestedArr
+  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) =>
+        formatSensorThingsApiResponseForLineChart(observationsArr)
     );
 
-  // Extract the observations and metadata arrays of arrays
-  const [observationsNestedArr, metadataNestedArr] =
-    observationsPlusMetadataArr;
-
-  // Format the observations
-  const formattedObservationsNestedArr = observationsNestedArr.map(
-    (observationsArr) =>
-      formatSensorThingsApiResponseForLineChart(observationsArr)
-  );
-
-  // Format the metadata
-  const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) =>
-    formatDatastreamMetadataForChart(metadataArr)
-  );
-
-  // Extract the formatted metadata properties
-  const extractedFormattedDatastreamProperties =
-    extractPropertiesFromFormattedDatastreamMetadata(
-      formattedMetadataNestedArr
+    // Format the metadata
+    const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) =>
+      formatDatastreamMetadataForChart(metadataArr)
     );
 
-  drawLineChartHighcharts(
-    formattedObservationsNestedArr,
-    extractedFormattedDatastreamProperties
-  );
+    // Extract the formatted metadata properties
+    const extractedFormattedDatastreamProperties =
+      extractPropertiesFromFormattedDatastreamMetadata(
+        formattedMetadataNestedArr
+      );
+
+    drawLineChartHighcharts(
+      formattedObservationsNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } catch (err) {
+    console.error(err);
+  }
 };
 
 /**
  * Test drawing of column chart using aggregation / sum result - monthly
  */
 const drawColumnChartMonthlySumTest = async function () {
-  const sensorsOfInterestNestedArr = [
-    ["125", "vl", "60min"],
-    ["225", "vl", "60min"],
-  ];
-
-  const observationsPlusMetadata =
-    await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-      BASE_URL,
-      QUERY_PARAMS_COMBINED,
-      sensorsOfInterestNestedArr
+  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
+      )
     );
 
-  // Extract the observations and metadata for each sensor
-  // Array elements in same order as input array
-  const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-  // 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"
+    // Unique calendar dates
+    const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
+      (observationsArr) =>
+        extractUniqueCalendarDatesFromTimestamp(observationsArr)
     );
 
-  // Format the observations
-  const formattedObservationsSumMonthlyNestedArr =
-    observationsSumMonthlyNestedArr.map((obsSumMonthlyArr, i) =>
-      formatAggregationResultForColumnChart(
-        uniqueCalendarMonthsNestedArr[i],
-        obsSumMonthlyArr
-      )
+    // Unique calendar months
+    const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
+      (uniqueCalendarDatesArr) =>
+        extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
     );
 
-  // Format the metadata
-  const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-    formatDatastreamMetadataForChart(metadataObj)
-  );
-
-  // Extract the formatted metadata properties
-  const extractedFormattedDatastreamProperties =
-    extractPropertiesFromFormattedDatastreamMetadata(
-      formattedMetadataNestedArr
+    // 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)
     );
 
-  drawColumnChartHighcharts(
-    formattedObservationsSumMonthlyNestedArr,
-    extractedFormattedDatastreamProperties
-  );
+    // Extract the formatted metadata properties
+    const extractedFormattedDatastreamProperties =
+      extractPropertiesFromFormattedDatastreamMetadata(
+        formattedMetadataNestedArr
+      );
+
+    drawColumnChartHighcharts(
+      formattedObservationsSumMonthlyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } catch (err) {
+    console.error(err);
+  }
 };
 
 /**
  * Test drawing of column chart using aggregation / sum result - daily
  */
 const drawColumnChartDailySumTest = async function () {
-  const sensorsOfInterestNestedArr = [
-    ["125", "vl", "60min"],
-    ["225", "vl", "60min"],
-  ];
-
-  const observationsPlusMetadata =
-    await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-      BASE_URL,
-      QUERY_PARAMS_COMBINED,
-      sensorsOfInterestNestedArr
+  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
+      )
     );
 
-  // Extract the observations and metadata for each sensor
-  // Array elements in same order as input array
-  const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-  // Unique calendar dates
-  const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
-    (observationsArr) =>
-      extractUniqueCalendarDatesFromTimestamp(observationsArr)
-  );
-
-  // Calculate sum of values of observations - daily
-  const observationsSumDailyNestedArr =
-    calculateSumOfObservationValuesWithinInterval(
-      observationsNestedArr,
-      "60min",
-      uniqueCalendarDatesNestedArr,
-      "daily"
+    // Unique calendar dates
+    const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
+      (observationsArr) =>
+        extractUniqueCalendarDatesFromTimestamp(observationsArr)
     );
 
-  // Format the observations - daily
-  const formattedObservationsSumDailyNestedArr =
-    observationsSumDailyNestedArr.map((obsSumDailyArr, i) =>
-      formatAggregationResultForColumnChart(
-        uniqueCalendarDatesNestedArr[i],
-        obsSumDailyArr
-      )
+    // 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)
     );
 
-  // Format the metadata
-  const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-    formatDatastreamMetadataForChart(metadataObj)
-  );
+    // Extract the formatted metadata properties
+    const extractedFormattedDatastreamProperties =
+      extractPropertiesFromFormattedDatastreamMetadata(
+        formattedMetadataNestedArr
+      );
 
-  // Extract the formatted metadata properties
-  const extractedFormattedDatastreamProperties =
-    extractPropertiesFromFormattedDatastreamMetadata(
-      formattedMetadataNestedArr
+    drawColumnChartHighcharts(
+      formattedObservationsSumDailyNestedArr,
+      extractedFormattedDatastreamProperties
     );
-
-  drawColumnChartHighcharts(
-    formattedObservationsSumDailyNestedArr,
-    extractedFormattedDatastreamProperties
-  );
+  } catch (err) {
+    console.error(err);
+  }
 };
 
 /**
  * Test drawing of line chart using aggregation / average result - monthly
  */
 const drawLineChartMonthlyAverageTest = async function () {
-  const sensorsOfInterestNestedArr = [
-    ["125", "vl", "60min"],
-    ["225", "vl", "60min"],
-  ];
-
-  const observationsPlusMetadata =
-    await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-      BASE_URL,
-      QUERY_PARAMS_COMBINED,
-      sensorsOfInterestNestedArr
+  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
+      )
     );
 
-  // Extract the observations and metadata for each sensor
-  // Array elements in same order as input array
-  const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-  // 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"
+    // Unique calendar dates
+    const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
+      (observationsArr) =>
+        extractUniqueCalendarDatesFromTimestamp(observationsArr)
     );
 
-  // Format the observations
-  const formattedObservationsAverageMonthlyNestedArr =
-    observationsAverageMonthlyNestedArr.map((obsAverageMonthlyArr, i) =>
-      formatAggregationResultForColumnChart(
-        uniqueCalendarMonthsNestedArr[i],
-        obsAverageMonthlyArr
-      )
+    // Unique calendar months
+    const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
+      (uniqueCalendarDatesArr) =>
+        extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
     );
 
-  //  Format the metadata
-  const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-    formatDatastreamMetadataForChart(metadataObj)
-  );
-
-  // Extract the formatted metadata properties
-  const extractedFormattedDatastreamProperties =
-    extractPropertiesFromFormattedDatastreamMetadata(
-      formattedMetadataNestedArr
+    // Calculate average of values of observations - monthly
+    const observationsAverageMonthlyNestedArr =
+      calculateAverageOfObservationValuesWithinInterval(
+        observationsNestedArr,
+        "60min",
+        uniqueCalendarMonthsNestedArr,
+        "monthly"
+      );
+
+    // Format the observations
+    const formattedObservationsAverageMonthlyNestedArr =
+      observationsAverageMonthlyNestedArr.map((obsAverageMonthlyArr, i) =>
+        formatAggregationResultForColumnChart(
+          uniqueCalendarMonthsNestedArr[i],
+          obsAverageMonthlyArr
+        )
+      );
+
+    // Format the metadata
+    const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
+      formatDatastreamMetadataForChart(metadataObj)
     );
 
-  drawLineChartHighcharts(
-    formattedObservationsAverageMonthlyNestedArr,
-    extractedFormattedDatastreamProperties
-  );
+    // Extract the formatted metadata properties
+    const extractedFormattedDatastreamProperties =
+      extractPropertiesFromFormattedDatastreamMetadata(
+        formattedMetadataNestedArr
+      );
+
+    drawLineChartHighcharts(
+      formattedObservationsAverageMonthlyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } catch (err) {
+    console.error(err);
+  }
 };
 
 /**
  * Test drawing of line chart using aggregation / average result - daily
  */
 const drawLineChartDailyAverageTest = async function () {
-  const sensorsOfInterestNestedArr = [
-    ["125", "vl", "60min"],
-    ["225", "vl", "60min"],
-  ];
-
-  const observationsPlusMetadata =
-    await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
-      BASE_URL,
-      QUERY_PARAMS_COMBINED,
-      sensorsOfInterestNestedArr
+  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
+      )
     );
 
-  // Extract the observations and metadata for each sensor
-  // Array elements in same order as input array
-  const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
-
-  // Unique calendar dates
-  const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
-    (observationsArr) =>
-      extractUniqueCalendarDatesFromTimestamp(observationsArr)
-  );
-
-  // Calculate average of values of observations - daily
-  const observationsAverageDailyNestedArr =
-    calculateAverageOfObservationValuesWithinInterval(
-      observationsNestedArr,
-      "60min",
-      uniqueCalendarDatesNestedArr,
-      "daily"
+    // Unique calendar dates
+    const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
+      (observationsArr) =>
+        extractUniqueCalendarDatesFromTimestamp(observationsArr)
     );
 
-  // Format the observations - daily
-  const formattedObservationsAverageDailyNestedArr =
-    observationsAverageDailyNestedArr.map((obsAverageDailyArr, i) =>
-      formatAggregationResultForColumnChart(
-        uniqueCalendarDatesNestedArr[i],
-        obsAverageDailyArr
-      )
+    // 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)
     );
 
-  // Format the metadata
-  const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-    formatDatastreamMetadataForChart(metadataObj)
-  );
+    // Extract the formatted metadata properties
+    const extractedFormattedDatastreamProperties =
+      extractPropertiesFromFormattedDatastreamMetadata(
+        formattedMetadataNestedArr
+      );
 
-  // Extract the formatted metadata properties
-  const extractedFormattedDatastreamProperties =
-    extractPropertiesFromFormattedDatastreamMetadata(
-      formattedMetadataNestedArr
+    drawLineChartHighcharts(
+      formattedObservationsAverageDailyNestedArr,
+      extractedFormattedDatastreamProperties
     );
-
-  drawLineChartHighcharts(
-    formattedObservationsAverageDailyNestedArr,
-    extractedFormattedDatastreamProperties
-  );
+  } catch (err) {
+    console.error(err);
+  }
 };
 
 // drawScatterPlotHCTest2();
diff --git a/public/js/src_modules/aggregate.mjs b/public/js/src_modules/aggregate.mjs
index 6c6a30c7b0f8f48642015bad8085823746873cf6..1174ffc0543100212d46fe6eec644ca40efae4a2 100644
--- a/public/js/src_modules/aggregate.mjs
+++ b/public/js/src_modules/aggregate.mjs
@@ -1,151 +1,9 @@
 "use strict";
 
-/**
- * Create 24-hour time strings for a time interval delimited by a start time and an end time. It is assumed that the start time is at "00:00:00" and the end time is at "23:45:00" (when the sampling rate of observations is 15 min) or "23:00:00" (when the sampling rate of observations is 60 min)
- * @param {String} phenomenonSamplingRate The sampling rate of the phenomenon of interest represented as a string, e.g. "15min", "60min"
- * @returns {Array} An array of two 24-hour strings representing the start time and end time
- */
-const createTimeStringsForInterval = function (phenomenonSamplingRate) {
-  const fifteenMinutes = "15min";
-  const sixtyMinutes = "60min";
-
-  const startTime = "00:00:00";
-  const endTimeFifteenMinutes = "23:45:00";
-  const endTimeSixtyMinutes = "23:00:00";
-
-  if (
-    phenomenonSamplingRate !== fifteenMinutes &&
-    phenomenonSamplingRate !== sixtyMinutes
-  )
-    throw new Error(
-      `Check that the provided phenomenon sampling rate string is in this format: "15min" or "60min"`
-    );
-
-  // 15 min sampling rate
-  if (phenomenonSamplingRate === fifteenMinutes) {
-    return [startTime, endTimeFifteenMinutes];
-  }
-
-  // 60 min sampling rate
-  if (phenomenonSamplingRate === sixtyMinutes) {
-    return [startTime, endTimeSixtyMinutes];
-  }
-};
-
-/**
- * Create an ISO 8601 date and time string
- * @param {String} inputCalendarDate Calendar date string in "YYYY-MM-DD" format
- * @param {String} inputTwentyFourHourTime 24-hour time string in "hh:mm:ss" format
- * @returns {String} An ISO 8601 date and time string
- */
-const createIso8601DateTimeString = function (
-  inputCalendarDate,
-  inputTwentyFourHourTime
-) {
-  return `${inputCalendarDate}T${inputTwentyFourHourTime}.000Z`;
-};
-
-/**
- * Check whether a year is a leap year or not
- * @param {Number} year Integer representing the year
- * @returns {Boolean} true if leap year, false if not
- */
-const checkIfLeapYear = function (year) {
-  return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
-};
-
-/**
- * Calculate the index of a timestamp in an array of timestamps
- * @param {Array} inputTimestampArr An array of timestamps, extracted from an array of observations
- * @param {String} timestampOfInterest A string representing the timestamp of interest in ISO 8601 format
- * @returns {Number} An integer representing the index of the timestamp of interest in the array of timestamps
- */
-const getIndexOfTimestamp = function (inputTimestampArr, timestampOfInterest) {
-  const timestampIndex = inputTimestampArr.findIndex(
-    (timestamp) => timestamp === timestampOfInterest
-  );
-
-  // If the timestamp does not exist in the timestamp array
-  if (timestampIndex === -1)
-    throw new Error(
-      "A start or end timestamp could not be found in the timestamp array"
-    );
-
-  // If the timestamp exists in the timestamp array
-  return timestampIndex;
-};
-
-/**
- * Calculate the indexes of the start and end timestamps
- * @param {Array} obsTimestampArr An array of observations timestamps
- * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
- * @param {String} startDate A 24-hour date string representing the start date
- * @param {String} endDate A 24-hour date string representing the end date
- * @returns {Array} A 1*2 array tht contains integers representing the start index and end index respectively
- */
-const calculateIndexStartEndTimestamp = function (
-  obsTimestampArr,
-  samplingRate,
-  startDate,
-  endDate
-) {
-  // Create and extract 24-hour strings for the start and end of interval
-  const [startTimeString, endTimeString] =
-    createTimeStringsForInterval(samplingRate);
-
-  // Create ISO 8601 strings for the start and end of interval
-  const startIso8601DateTimeString = createIso8601DateTimeString(
-    startDate,
-    startTimeString
-  );
-  const endIso8601DateTimeString = createIso8601DateTimeString(
-    endDate,
-    endTimeString
-  );
-
-  // Calculate the indexes of the timestamps for the start and end of interval
-  const indexStartTimestamp = getIndexOfTimestamp(
-    obsTimestampArr,
-    startIso8601DateTimeString
-  );
-  const indexEndTimestamp = getIndexOfTimestamp(
-    obsTimestampArr,
-    endIso8601DateTimeString
-  );
-
-  return [indexStartTimestamp, indexEndTimestamp];
-};
-
-/**
- * Extract the set of observation values that fall within a time interval delimited by a start date and end date. The start date may be the same as the end date.
- * @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
- * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
- * @param {String} startDate A 24-hour date string representing the start date
- * @param {String} endDate A 24-hour date string representing the end date
- * @returns {Array} An array of observation values that fall within our time interval
- */
-const extractObservationValuesWithinDatesInterval = function (
-  obsArray,
-  samplingRate,
-  startDate,
-  endDate
-) {
-  // Extract the timestamps and values from the observations
-  const obsTimestampArr = obsArray.map((obs) => obs[0]);
-  const obsValuesArr = obsArray.map((obs) => obs[1]);
-
-  // Calculate the indexes of the timestamps for the start and end of interval
-  const [indexStartTimestamp, indexEndTimestamp] =
-    calculateIndexStartEndTimestamp(
-      obsTimestampArr,
-      samplingRate,
-      startDate,
-      endDate
-    );
-
-  // Extract the observations that fall within our time interval
-  return obsValuesArr.slice(indexStartTimestamp, indexEndTimestamp + 1);
-};
+import {
+  extractObservationValuesWithinDatesInterval,
+  extractObservationValuesWithinMonthInterval,
+} from "./aggregateHelpers.mjs";
 
 /**
  * Calculate the sum of observation values that fall within a time interval delimited by a start date and end date
@@ -175,82 +33,6 @@ const calculateAverageOfObservationValuesWithinDatesInterval = function (
   );
 };
 
-/**
- * Extract the year and month digits from a calendar month string
- * @param {String} calendarMonthStr Calendar month string in "YYYY-MM" format
- * @returns {Array} A 1*2 array tht contains integers representing the year and month respectively
- */
-const extractMonthYearDigitsFromCalendarMonthString = function (
-  calendarMonthStr
-) {
-  // Extract year as integer
-  const yearNum = parseInt(calendarMonthStr.slice(0, 4), 10);
-
-  // Extract month as integer
-  const monthNum = parseInt(calendarMonthStr.slice(-2), 10);
-
-  return [yearNum, monthNum];
-};
-
-/**
- * Extract the set of observation values that fall within a time interval delimited by the first day and last day of a calendar month
- * @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
- * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
- * @param {String} calendarMonthStr Calendar month string in "YYYY-MM" format
- * @returns {Array} An array of observation values that fall within one calendar month
- */
-const extractObservationValuesWithinMonthInterval = function (
-  obsArray,
-  samplingRate,
-  calendarMonthStr
-) {
-  // Extract the year and month digits from the calendar month string
-  const [yearNum, monthNum] =
-    extractMonthYearDigitsFromCalendarMonthString(calendarMonthStr);
-
-  if (monthNum < 1 || monthNum > 12) return;
-
-  // All the months start on the first
-  const startDateStr = `${calendarMonthStr}-01`;
-
-  // February
-  if (monthNum === 2) {
-    // Leap year
-    if (checkIfLeapYear(yearNum))
-      return extractObservationValuesWithinDatesInterval(
-        obsArray,
-        samplingRate,
-        startDateStr,
-        `${calendarMonthStr}-29`
-      );
-
-    // Non-leap year
-    return extractObservationValuesWithinDatesInterval(
-      obsArray,
-      samplingRate,
-      startDateStr,
-      `${calendarMonthStr}-28`
-    );
-  }
-
-  // Months with 30 days
-  if (monthNum === 4 || monthNum === 6 || monthNum === 9 || monthNum === 11)
-    return extractObservationValuesWithinDatesInterval(
-      obsArray,
-      samplingRate,
-      startDateStr,
-      `${calendarMonthStr}-30`
-    );
-
-  // Months with 31 days
-  return extractObservationValuesWithinDatesInterval(
-    obsArray,
-    samplingRate,
-    startDateStr,
-    `${calendarMonthStr}-31`
-  );
-};
-
 /**
  * Calculate the sum of observation values that fall within a time interval delimited by the first day and last day of a calendar month
  * @param {Array} obsValuesForMonthIntervalArr An array of observation values that fall within one calendar month
@@ -375,46 +157,7 @@ const calculateAverageOfObservationValuesWithinInterval = function (
   }
 };
 
-/**
- * Extract unique calendar dates from date/time strings in ISO 8601 format
- * @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
- * @returns {Array} An array of unique calendar date strings in "YYYY-MM-DD" format
- */
-const extractUniqueCalendarDatesFromTimestamp = function (obsArray) {
-  // The timestamp is the first element of the observation array
-  const timestampArr = obsArray.map((obs) => obs[0]);
-
-  // Extract the calendar date string from the timestamp string
-  const calendarDateArr = timestampArr.map((timestamp) =>
-    timestamp.slice(0, 10)
-  );
-
-  // Use a set to remove duplicates
-  const uniqueCalendarDates = new Set(calendarDateArr);
-
-  return [...uniqueCalendarDates];
-};
-
-/**
- * Extract unique calendar months from calendar date strings
- * @param {Array} calendarDatesArr An array of unique calendar date strings in "YYYY-MM-DD" format
- * @returns {Array} An array of unique calendar month strings in "YYYY-MM" format
- */
-const extractUniqueCalendarMonthsFromCalendarDates = function (
-  calendarDatesArr
-) {
-  // Extract the calendar month strings
-  const calendarMonthsArr = calendarDatesArr.map((date) => date.slice(0, 7));
-
-  // Use a set to remove duplicates
-  const uniqueCalendarMonths = new Set(calendarMonthsArr);
-
-  return [...uniqueCalendarMonths];
-};
-
 export {
   calculateSumOfObservationValuesWithinInterval,
-  extractUniqueCalendarDatesFromTimestamp,
-  extractUniqueCalendarMonthsFromCalendarDates,
   calculateAverageOfObservationValuesWithinInterval,
 };
diff --git a/public/js/src_modules/aggregateHelpers.mjs b/public/js/src_modules/aggregateHelpers.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..7df61a993d04e61174eefa0cfe6a808786608e56
--- /dev/null
+++ b/public/js/src_modules/aggregateHelpers.mjs
@@ -0,0 +1,298 @@
+"use strict";
+
+/**
+ * Create 24-hour time strings for a time interval delimited by a start time and an end time. It is assumed that the start time is at "00:00:00" and the end time is at "23:45:00" (when the sampling rate of observations is 15 min) or "23:00:00" (when the sampling rate of observations is 60 min)
+ * @param {String} phenomenonSamplingRate The sampling rate of the phenomenon of interest represented as a string, e.g. "15min", "60min"
+ * @returns {Array} An array of two 24-hour strings representing the start time and end time
+ */
+const createTimeStringsForInterval = function (phenomenonSamplingRate) {
+  const fifteenMinutes = "15min";
+  const sixtyMinutes = "60min";
+
+  const startTime = "00:00:00";
+  const endTimeFifteenMinutes = "23:45:00";
+  const endTimeSixtyMinutes = "23:00:00";
+
+  if (
+    phenomenonSamplingRate !== fifteenMinutes &&
+    phenomenonSamplingRate !== sixtyMinutes
+  )
+    throw new Error(
+      `Check that the provided phenomenon sampling rate string is in this format: "15min" or "60min"`
+    );
+
+  // 15 min sampling rate
+  if (phenomenonSamplingRate === fifteenMinutes) {
+    return [startTime, endTimeFifteenMinutes];
+  }
+  // 60 min sampling rate
+  if (phenomenonSamplingRate === sixtyMinutes) {
+    return [startTime, endTimeSixtyMinutes];
+  }
+};
+
+/**
+ * Create an ISO 8601 date and time string
+ * @param {String} inputCalendarDate Calendar date string in "YYYY-MM-DD" format
+ * @param {String} inputTwentyFourHourTime 24-hour time string in "hh:mm:ss" format
+ * @returns {String} An ISO 8601 date and time string
+ */
+const createIso8601DateTimeString = function (
+  inputCalendarDate,
+  inputTwentyFourHourTime
+) {
+  return `${inputCalendarDate}T${inputTwentyFourHourTime}.000Z`;
+};
+
+/**
+ * Check whether a year is a leap year or not
+ * @param {Number} year Integer representing the year
+ * @returns {Boolean} true if leap year, false if not
+ */
+const checkIfLeapYear = function (year) {
+  return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+};
+
+/**
+ * Calculate the index of a timestamp in an array of timestamps
+ * @param {Array} inputTimestampArr An array of timestamps, extracted from an array of observations
+ * @param {String} timestampOfInterest A string representing the timestamp of interest in ISO 8601 format
+ * @returns {Number} An integer representing the index of the timestamp of interest in the array of timestamps
+ */
+const getIndexOfTimestamp = function (inputTimestampArr, timestampOfInterest) {
+  const timestampIndex = inputTimestampArr.findIndex(
+    (timestamp) => timestamp === timestampOfInterest
+  );
+
+  // If the timestamp does not exist in the timestamp array
+  if (timestampIndex === -1)
+    throw new Error(
+      "A start or end timestamp could not be found in the timestamp array"
+    );
+
+  // If the timestamp exists in the timestamp array
+  return timestampIndex;
+};
+
+/**
+ * Calculate the indexes of the start and end timestamps
+ * @param {Array} obsTimestampArr An array of observations timestamps
+ * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
+ * @param {String} startDate A calendar date string in "YYYY-MM-DD" format representing the start date
+ * @param {String} endDate A calendar date string in "YYYY-MM-DD" format representing the end date
+ * @returns {Array} A 1*2 array tht contains integers representing the start index and end index respectively
+ */
+const calculateIndexStartEndTimestamp = function (
+  obsTimestampArr,
+  samplingRate,
+  startDate,
+  endDate
+) {
+  // Create and extract 24-hour strings for the start and end of interval
+  const [startTimeString, endTimeString] =
+    createTimeStringsForInterval(samplingRate);
+
+  // Create ISO 8601 strings for the start and end of interval
+  const startIso8601DateTimeString = createIso8601DateTimeString(
+    startDate,
+    startTimeString
+  );
+  const endIso8601DateTimeString = createIso8601DateTimeString(
+    endDate,
+    endTimeString
+  );
+
+  // Calculate the indexes of the timestamps for the start and end of interval
+  const indexStartTimestamp = getIndexOfTimestamp(
+    obsTimestampArr,
+    startIso8601DateTimeString
+  );
+  const indexEndTimestamp = getIndexOfTimestamp(
+    obsTimestampArr,
+    endIso8601DateTimeString
+  );
+
+  return [indexStartTimestamp, indexEndTimestamp];
+};
+
+/**
+ * Extract the set of observations that fall within a time interval delimited by a start date and end date. The start date may be the same as the end date.
+ * @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
+ * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
+ * @param {String} startDate A calendar date string in "YYYY-MM-DD" format representing the start date
+ * @param {String} endDate A calendar date string in "YYYY-MM-DD" format representing the end date
+ * @returns {Array} An array of observations (timestamp + value) that fall within our time interval
+ */
+const extractObservationsWithinDatesInterval = function (
+  obsArray,
+  samplingRate,
+  startDate,
+  endDate
+) {
+  // Extract the timestamps from the observations
+  const obsTimestampArr = obsArray.map((obs) => obs[0]);
+
+  // Calculate the indexes of the timestamps for the start and end of interval
+  const [indexStartTimestamp, indexEndTimestamp] =
+    calculateIndexStartEndTimestamp(
+      obsTimestampArr,
+      samplingRate,
+      startDate,
+      endDate
+    );
+
+  // Extract the observations that fall within our time interval
+  return obsArray.slice(indexStartTimestamp, indexEndTimestamp + 1);
+};
+
+/**
+ * Extract the set of observation values that fall within a time interval delimited by a start date and end date. The start date may be the same as the end date.
+ * @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
+ * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
+ * @param {String} startDate A calendar date string in "YYYY-MM-DD" format representing the start date
+ * @param {String} endDate A calendar date string in "YYYY-MM-DD" format representing the end date
+ * @returns {Array} An array of observation values that fall within our time interval
+ */
+const extractObservationValuesWithinDatesInterval = function (
+  obsArray,
+  samplingRate,
+  startDate,
+  endDate
+) {
+  // Extract the timestamps and values from the observations
+  const obsTimestampArr = obsArray.map((obs) => obs[0]);
+  const obsValuesArr = obsArray.map((obs) => obs[1]);
+
+  // Calculate the indexes of the timestamps for the start and end of interval
+  const [indexStartTimestamp, indexEndTimestamp] =
+    calculateIndexStartEndTimestamp(
+      obsTimestampArr,
+      samplingRate,
+      startDate,
+      endDate
+    );
+
+  // Extract the observation values that fall within our time interval
+  return obsValuesArr.slice(indexStartTimestamp, indexEndTimestamp + 1);
+};
+
+/**
+ * Extract the year and month digits from a calendar month string
+ * @param {String} calendarMonthStr Calendar month string in "YYYY-MM" format
+ * @returns {Array} A 1*2 array tht contains integers representing the year and month respectively
+ */
+const extractMonthYearDigitsFromCalendarMonthString = function (
+  calendarMonthStr
+) {
+  // Extract year as integer
+  const yearNum = parseInt(calendarMonthStr.slice(0, 4), 10);
+
+  // Extract month as integer
+  const monthNum = parseInt(calendarMonthStr.slice(-2), 10);
+
+  return [yearNum, monthNum];
+};
+
+/**
+ * Extract the set of observation values that fall within a time interval delimited by the first day and last day of a calendar month
+ * @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
+ * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
+ * @param {String} calendarMonthStr Calendar month string in "YYYY-MM" format
+ * @returns {Array} An array of observation values that fall within one calendar month
+ */
+const extractObservationValuesWithinMonthInterval = function (
+  obsArray,
+  samplingRate,
+  calendarMonthStr
+) {
+  // Extract the year and month digits from the calendar month string
+  const [yearNum, monthNum] =
+    extractMonthYearDigitsFromCalendarMonthString(calendarMonthStr);
+
+  if (monthNum < 1 || monthNum > 12) return;
+
+  // All the months start on the first
+  const startDateStr = `${calendarMonthStr}-01`;
+
+  // February
+  if (monthNum === 2) {
+    // Leap year
+    if (checkIfLeapYear(yearNum))
+      return extractObservationValuesWithinDatesInterval(
+        obsArray,
+        samplingRate,
+        startDateStr,
+        `${calendarMonthStr}-29`
+      );
+
+    // Non-leap year
+    return extractObservationValuesWithinDatesInterval(
+      obsArray,
+      samplingRate,
+      startDateStr,
+      `${calendarMonthStr}-28`
+    );
+  }
+
+  // Months with 30 days
+  if (monthNum === 4 || monthNum === 6 || monthNum === 9 || monthNum === 11)
+    return extractObservationValuesWithinDatesInterval(
+      obsArray,
+      samplingRate,
+      startDateStr,
+      `${calendarMonthStr}-30`
+    );
+
+  // Months with 31 days
+  return extractObservationValuesWithinDatesInterval(
+    obsArray,
+    samplingRate,
+    startDateStr,
+    `${calendarMonthStr}-31`
+  );
+};
+
+/**
+ * Extract unique calendar dates from date/time strings in ISO 8601 format
+ * @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
+ * @returns {Array} An array of unique calendar date strings in "YYYY-MM-DD" format
+ */
+const extractUniqueCalendarDatesFromTimestamp = function (obsArray) {
+  // The timestamp is the first element of the observation array
+  const timestampArr = obsArray.map((obs) => obs[0]);
+
+  // Extract the calendar date string from the timestamp string
+  const calendarDateArr = timestampArr.map((timestamp) =>
+    timestamp.slice(0, 10)
+  );
+
+  // Use a set to remove duplicates
+  const uniqueCalendarDates = new Set(calendarDateArr);
+
+  return [...uniqueCalendarDates];
+};
+
+/**
+ * Extract unique calendar months from calendar date strings
+ * @param {Array} calendarDatesArr An array of unique calendar date strings in "YYYY-MM-DD" format
+ * @returns {Array} An array of unique calendar month strings in "YYYY-MM" format
+ */
+const extractUniqueCalendarMonthsFromCalendarDates = function (
+  calendarDatesArr
+) {
+  // Extract the calendar month strings
+  const calendarMonthsArr = calendarDatesArr.map((date) => date.slice(0, 7));
+
+  // Use a set to remove duplicates
+  const uniqueCalendarMonths = new Set(calendarMonthsArr);
+
+  return [...uniqueCalendarMonths];
+};
+
+export {
+  extractObservationsWithinDatesInterval,
+  extractObservationValuesWithinDatesInterval,
+  extractObservationValuesWithinMonthInterval,
+  extractUniqueCalendarDatesFromTimestamp,
+  extractUniqueCalendarMonthsFromCalendarDates,
+};
diff --git a/public/js/src_modules/chartColumn.mjs b/public/js/src_modules/chartColumn.mjs
index 9f259f307b0de2944da33f7b596973bdbea814e5..1da6590c29060ef74d51c77b60e1e840903958d3 100644
--- a/public/js/src_modules/chartColumn.mjs
+++ b/public/js/src_modules/chartColumn.mjs
@@ -1,6 +1,6 @@
 "use strict";
 
-import { chartExportOptions } from "./chartExport.mjs";
+import { chartExportOptions } from "./chartHelpers.mjs";
 
 /**
  * Format a computed aggregation result to make it suitable for a column chart
@@ -42,6 +42,13 @@ const createSeriesOptionsForColumnChart = function (
   // Create an array of seriesOptions objects
   // Assumes that the observation array of arrays, phenomenon names array and phenomenon symbols array are of equal length
   // Use one of the arrays for looping
+  if (
+    formattedAggregatedResultForColumnChart.length !== phenomenonNamesArr.length
+  )
+    throw new Error(
+      "The observations array and phenomenon names array have different lengths"
+    );
+
   return formattedAggregatedResultForColumnChart.map(
     (formattedAggResArray, i) => {
       return {
@@ -57,7 +64,7 @@ const createSeriesOptionsForColumnChart = function (
  * Draw a column chart using Highcharts library
  * @param {Array} formattedAggResultArraysForColumnChart An array made up of formatted aggregated result array(s) suitable for use in a column chart
  * @param {Object} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties
- * @returns {undefined}
+ * @returns {undefined} undefined
  */
 const drawColumnChartHighcharts = function (
   formattedAggResultArraysForColumnChart,
diff --git a/public/js/src_modules/chartExport.mjs b/public/js/src_modules/chartExport.mjs
deleted file mode 100644
index 0e26d0edbf8637eb7813d7d3483bfeeab100c1af..0000000000000000000000000000000000000000
--- a/public/js/src_modules/chartExport.mjs
+++ /dev/null
@@ -1,9 +0,0 @@
-"use strict";
-
-export const chartExportOptions = {
-  buttons: {
-    contextButton: {
-      menuItems: ["downloadPNG", "downloadJPEG", "downloadPDF", "downloadSVG"],
-    },
-  },
-};
diff --git a/public/js/src_modules/chartHeatmap.mjs b/public/js/src_modules/chartHeatmap.mjs
index 50534b41b2b560439dde1b8e22a61fa20a60baf4..a48c5bd582d503b531a2b18e71768b65cdc8284b 100644
--- a/public/js/src_modules/chartHeatmap.mjs
+++ b/public/js/src_modules/chartHeatmap.mjs
@@ -1,6 +1,6 @@
 "use strict";
 
-import { chartExportOptions } from "./chartExport.mjs";
+import { chartExportOptions } from "./chartHelpers.mjs";
 
 /**
  * Format the response from SensorThings API to make it suitable for use in a heatmap
diff --git a/public/js/src_modules/chartHelpers.mjs b/public/js/src_modules/chartHelpers.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..10f3d51eeed8c26ef24cd791a13aa5de987a8cb6
--- /dev/null
+++ b/public/js/src_modules/chartHelpers.mjs
@@ -0,0 +1,78 @@
+"use strict";
+
+const chartExportOptions = {
+  buttons: {
+    contextButton: {
+      menuItems: ["downloadPNG", "downloadJPEG", "downloadPDF", "downloadSVG"],
+    },
+  },
+};
+
+/**
+ * Convert a hexadecimal color code obtained from the Highcharts object (`Highcharts.getOptions().colors`) to its equivalent RGB color code
+ * @param {String} hexCode Input hex color code
+ * @returns {String} Output RGB color code
+ */
+const convertHexColorToRGBColor = function (hexCode) {
+  const hexToRGBMapping = {
+    "#7cb5ec": "rgb(124, 181, 236)",
+    "#434348": "rgb(67, 67, 72)",
+    "#90ed7d": "rgb(144, 237, 125)",
+    "#f7a35c": "rgb(247, 163, 92)",
+    "#8085e9": "rgb(128, 133, 233)",
+    "#f15c80": "rgb(241, 92, 128)",
+    "#e4d354": "rgb(228, 211, 84)",
+    "#2b908f": "rgb(228, 211, 84)",
+    "#f45b5b": "rgb(244, 91, 91)",
+    "#91e8e1": "rgb(145, 232, 225)",
+  };
+
+  if (hexToRGBMapping?.[hexCode] === undefined)
+    throw new Error(
+      "The provided hex code is not valid or is not supported by this function"
+    );
+
+  // Extract the RGB color elements as a single string
+  // The individual color elements are separated by commas
+  return (hexToRGBMapping?.[hexCode]).slice(4, -1);
+};
+
+/**
+ * Concatenates metadata properties into a single string with an ampersand as the delimiter
+ * @param {Array} metadataPropertiesArr An array of metadata property strings
+ * @returns {String} A string made up of combined metadata properties delimited by an ampersand
+ */
+const createCombinedTextDelimitedByAmpersand = function (
+  metadataPropertiesArr
+) {
+  return metadataPropertiesArr.join(" & ");
+};
+
+/**
+ * Concatenates metadata properties into a single string with a comma as the delimiter
+ * @param {Array} metadataPropertiesArr An array of metadata property strings
+ * @returns {String} A string made up of combined metadata properties delimited by a comma
+ */
+const createCombinedTextDelimitedByComma = function (metadataPropertiesArr) {
+  return metadataPropertiesArr.join(", ");
+};
+
+/**
+ * Extracts the sampling rate substring from a datastream name string
+ * @param {Array} datastreamNamesArr An array of datastream name(s)
+ * @returns {Array} An array containing the sampling rate substring(s)
+ */
+const extractSamplingRateFromDatastreamName = function (datastreamNamesArr) {
+  // The sampling rate string is the last word in the Datastream name string
+  return datastreamNamesArr.map((datastreamName) =>
+    datastreamName.split(" ").pop()
+  );
+};
+
+export {
+  chartExportOptions,
+  createCombinedTextDelimitedByAmpersand,
+  createCombinedTextDelimitedByComma,
+  convertHexColorToRGBColor,
+  extractSamplingRateFromDatastreamName,
+};
diff --git a/public/js/src_modules/chartLine.mjs b/public/js/src_modules/chartLine.mjs
index aaf3f4fd5774709d29cf353b07eece9505afd2a3..a8f66989cdfe508c9bdae28da6d3f9d8ed08a1dd 100644
--- a/public/js/src_modules/chartLine.mjs
+++ b/public/js/src_modules/chartLine.mjs
@@ -1,6 +1,9 @@
 "use strict";
 
-import { chartExportOptions } from "./chartExport.mjs";
+import {
+  chartExportOptions,
+  extractSamplingRateFromDatastreamName,
+} from "./chartHelpers.mjs";
 
 /**
  * Format the response from SensorThings API to make it suitable for use in a line chart
@@ -17,27 +20,13 @@ const formatSensorThingsApiResponseForLineChart = function (obsArray) {
   });
 };
 
-/**
- * Extracts the sampling rate substring from a datastream name string
- * @param {Array} datastreamNamesArr An array of datastream name(s)
- * @returns {Array} An array containing the sampling rate substring(s)
- */
-const extractSamplingRateFromDatastreamName = function (datastreamNamesArr) {
-  // The sampling rate string is the last word in the Datastream name string
-  return datastreamNamesArr.map((datastreamName) =>
-    datastreamName.split(" ").pop()
-  );
-};
-
 /**
  * Concatenates metadata properties to create a string for either the title or subtitle of a line chart
- * @param {Array} datastreamMetadataPropArr An array of metadata property strings
- * @returns {String} A string of comma separated metadata property strings
+ * @param {Array} phenomenonNamesArr An array of phenomenon name strings
+ * @returns {String} A string made up of combined phenomenon names
  */
-const createCombinedTextForLineChartTitles = function (
-  datastreamMetadataPropArr
-) {
-  return datastreamMetadataPropArr.join(", ");
+const createCombinedTextForLineChartTitles = function (phenomenonNamesArr) {
+  return phenomenonNamesArr.join(", ");
 };
 
 /**
@@ -50,17 +39,25 @@ const createSeriesOptionsForLineChart = function (
   formattedObsArraysForLineChart,
   phenomenonNamesArr
 ) {
-  // An array of colors provided by the Highcharts object
+  // An array of colors, in hexadecimal format, provided by the global Highcharts object
   const seriesColors = Highcharts.getOptions().colors;
 
+  // Create a copy of the colors array
+  const seriesColorsArr = [...seriesColors];
+
   // Create an array of seriesOptions objects
   // Assumes that the observation array of arrays and phenomenon names array are of equal length
   // Use one of the arrays for looping
+  if (formattedObsArraysForLineChart.length !== phenomenonNamesArr.length)
+    throw new Error(
+      "The observations array and phenomenon names array have different lengths"
+    );
+
   return formattedObsArraysForLineChart.map((formattedObsArray, i) => {
     return {
       name: `${phenomenonNamesArr[i]}`,
       data: formattedObsArray,
-      color: seriesColors[i],
+      color: seriesColorsArr[i],
       turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
     };
   });
@@ -83,6 +80,14 @@ const drawLineChartHighcharts = function (
     unitOfMeasurementSymbolsArr,
   } = extractedFormattedDatastreamProperties;
 
+  // Chart title and subtitle text
+  const textChartTitle =
+    createCombinedTextForLineChartTitles(phenomenonNamesArr);
+
+  const textChartSubtitle = `Sampling rate(s): ${createCombinedTextForLineChartTitles(
+    extractSamplingRateFromDatastreamName(datastreamNamesArr)
+  )}`;
+
   // Create the array of series options object(s)
   const seriesOptionsArr = createSeriesOptionsForLineChart(
     formattedObsArraysForLineChart,
@@ -100,14 +105,12 @@ const drawLineChartHighcharts = function (
     },
 
     title: {
-      text: createCombinedTextForLineChartTitles(phenomenonNamesArr),
+      text: textChartTitle,
       "align": "left",
     },
 
     subtitle: {
-      text: `Sampling rate(s): ${createCombinedTextForLineChartTitles(
-        extractSamplingRateFromDatastreamName(datastreamNamesArr)
-      )}`,
+      text: textChartSubtitle,
       align: "left",
     },
 
diff --git a/public/js/src_modules/chartScatterPlot.mjs b/public/js/src_modules/chartScatterPlot.mjs
index 09b8592d2bf9bf4cdb89f77d009b62c12ad3880c..3b9b878661e27bb363597eb254dc6de989053299 100644
--- a/public/js/src_modules/chartScatterPlot.mjs
+++ b/public/js/src_modules/chartScatterPlot.mjs
@@ -1,6 +1,12 @@
 "use strict";
 
-import { chartExportOptions } from "./chartExport.mjs";
+import {
+  chartExportOptions,
+  convertHexColorToRGBColor,
+  createCombinedTextDelimitedByAmpersand,
+  createCombinedTextDelimitedByComma,
+  extractSamplingRateFromDatastreamName,
+} from "./chartHelpers.mjs";
 
 /**
  * Determines the timestamps that are missing from a smaller set of observations. Based on the comparison of two observation arrays, where one array is larger than the other
@@ -12,15 +18,13 @@ const getSymmetricDifferenceBetweenArrays = function (
   obsTimestampArrayOne,
   obsTimestampArrayTwo
 ) {
-  const differenceBetweenArrays = obsTimestampArrayOne
+  return obsTimestampArrayOne
     .filter((timestampOne) => !obsTimestampArrayTwo.includes(timestampOne))
     .concat(
       obsTimestampArrayTwo.filter(
         (timestampTwo) => !obsTimestampArrayOne.includes(timestampTwo)
       )
     );
-
-  return differenceBetweenArrays;
 };
 
 /**
@@ -33,11 +37,9 @@ const getIndexesOfUniqueObservations = function (
   uniqueTimestampsArr,
   largerObsTimestampArr
 ) {
-  const indexesMissingObs = uniqueTimestampsArr.map((index) =>
+  return uniqueTimestampsArr.map((index) =>
     largerObsTimestampArr.indexOf(index)
   );
-
-  return indexesMissingObs;
 };
 
 /**
@@ -213,11 +215,134 @@ const formatSensorThingsApiResponseForScatterPlot = function (
   return createCombinedObservationValues(obsArrayOne, obsArrayTwo);
 };
 
+/**
+ * Concatenates metadata properties to create a string for either the title or subtitle of a scatter plot
+ * @param {Array} phenomenonNamesArr An array of phenomenon name strings
+ * @returns {String} A string made up of combined phenomenon names
+ */
+const createCombinedTextForScatterPlotTitles = function (phenomenonNamesArr) {
+  // x-axis phenomenon name is the first element of array
+  const phenomenonNameXAxis = phenomenonNamesArr[0];
+
+  // y-axis phenomenon name(s) array is remaining elements of array
+  const phenomenonNamesYAxisArr = phenomenonNamesArr.slice(1);
+  // Use a set to remove duplicates
+  const uniquePhenomenonNamesYAxis = new Set(phenomenonNamesYAxisArr);
+  const uniquePhenomenonNamesYAxisArr = [...uniquePhenomenonNamesYAxis];
+
+  return `${createCombinedTextDelimitedByAmpersand(
+    uniquePhenomenonNamesYAxisArr
+  )} versus ${phenomenonNameXAxis}`;
+};
+
+/**
+ * Create string for the x-axis title of a scatter plot
+ * @param {Array} phenomenonNamesArr Array of phenomenon name strings
+ * @param {Array} unitOfMeasurementSymbolsArr rray of unit of measurement symbol strings
+ * @returns {String} X-axis title string for scatter plot
+ */
+const createXAxisTitleTextScatterPlot = function (
+  phenomenonNamesArr,
+  unitOfMeasurementSymbolsArr
+) {
+  // x-axis phenomenon name string is first element of array
+  const phenomenonNameXAxis = phenomenonNamesArr[0];
+
+  // x-axis phenomenon symbol string is first element of array
+  const unitOfMeasurementSymbolXAxis = unitOfMeasurementSymbolsArr[0];
+
+  return `${phenomenonNameXAxis} [${unitOfMeasurementSymbolXAxis}]`;
+};
+
+/**
+ * Create string for the y-axis title of a scatter plot
+ * @param {Array} phenomenonNamesArr Array of phenomenon name strings
+ * @param {Array} unitOfMeasurementSymbolsArr Array of unit of measurement symbol strings
+ * @returns {String} Y-axis title string for scatter plot
+ */
+const createYAxisTitleTextScatterPlot = function (
+  phenomenonNamesArr,
+  unitOfMeasurementSymbolsArr
+) {
+  // y-axis phenomenon names start at array index 1
+  const phenomenonNamesYAxisArr = phenomenonNamesArr.slice(1);
+
+  // y-axis phenomenon symbols start at array index 1
+  const unitOfMeasurementSymbolsYAxisArr = unitOfMeasurementSymbolsArr.slice(1);
+
+  // The phenomenon names and unit of measurement arrays should have equal lengths
+  // Use one of the arrays for looping
+  if (
+    phenomenonNamesYAxisArr.length !== unitOfMeasurementSymbolsYAxisArr.length
+  )
+    throw new Error(
+      "The phenomenon names array and unit of measurement symbols array have different lengths"
+    );
+
+  const combinedNameSymbolArr = phenomenonNamesYAxisArr.map(
+    (phenomenonNameYAxis, i) =>
+      `${phenomenonNameYAxis} [${unitOfMeasurementSymbolsYAxisArr[i]}]`
+  );
+
+  return createCombinedTextDelimitedByComma(combinedNameSymbolArr);
+};
+
+/**
+ * Create an options object for each series drawn in the scatter plot
+ * @param {Array} formattedObsArraysForScatterPlot An array of formatted observation array(s) from one or more datastreams
+ * @param {Array} phenomenonNamesArr An array of phenomenon name(s)
+ * @returns {Array} An array made up of series options object(s)
+ */
+const createSeriesOptionsForScatterPlot = function (
+  formattedObsArraysForScatterPlot,
+  phenomenonNamesArr
+) {
+  // An array of colors, in hexadecimal format, provided by the global Highcharts object
+  const highchartsColorsArr = Highcharts.getOptions().colors;
+
+  // Create a reversed copy of the colors array
+  const highchartsColorsReversedArr = [...highchartsColorsArr].reverse();
+
+  // Opacity value for symbol
+  const SERIES_SYMBOL_COLOR_OPACITY = ".3";
+
+  // Create array of colors in RGBA format
+  const seriesColors = highchartsColorsReversedArr.map(
+    (hexColorCode) =>
+      `rgba(${convertHexColorToRGBColor(
+        hexColorCode
+      )}, ${SERIES_SYMBOL_COLOR_OPACITY})`
+  );
+
+  // x-axis phenomenon name is the first element of array
+  const phenomenonNameXAxis = phenomenonNamesArr[0];
+  // y-axis phenomenon name(s) array is remaining elements of array
+  const phenomenonNamesYAxisArr = phenomenonNamesArr.slice(1);
+
+  // Create an array of seriesOptions objects
+  // Assumes that the observation array of arrays and phenomenon names array are of equal length
+  // Use one of the arrays for looping
+  if (
+    formattedObsArraysForScatterPlot.length !== phenomenonNamesYAxisArr.length
+  )
+    throw new Error(
+      "The observations array and phenomenon names array have different lengths"
+    );
+
+  return formattedObsArraysForScatterPlot.map((formattedObsArray, i) => {
+    return {
+      name: `${phenomenonNamesYAxisArr[i]}, ${phenomenonNameXAxis}`,
+      data: formattedObsArray,
+      color: seriesColors[i],
+    };
+  });
+};
+
 /**
  * Draw a scatter plot using Highcharts library
  * @param {Array} formattedObsArrayForSeriesOnePlusSeriesTwo Response from SensorThings API formatted for use in a scatter plot
  * @param {Object} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties
- * @returns {undefined}
+ * @returns {undefined} undefined
  */
 const drawScatterPlotHighcharts = function (
   formattedObsArrayForSeriesOnePlusSeriesTwo,
@@ -231,32 +356,32 @@ const drawScatterPlotHighcharts = function (
     unitOfMeasurementSymbolsArr,
   } = extractedFormattedDatastreamProperties;
 
-  const [DATASTREAM_DESCRIPTION_SERIES_1, DATASTREAM_DESCRIPTION_SERIES_2] =
-    datastreamDescriptionsArr;
-  const [DATASTREAM_NAME_SERIES_1, DATASTREAM_NAME_SERIES_2] =
-    datastreamNamesArr;
-  const [PHENOMENON_NAME_SERIES_1, PHENOMENON_NAME_SERIES_2] =
-    phenomenonNamesArr;
-  const [PHENOMENON_SYMBOL_SERIES_1, PHENOMENON_SYMBOL_SERIES_2] =
-    unitOfMeasurementSymbolsArr;
+  // Create the array of series options object(s)
+  const seriesOptionsArr = createSeriesOptionsForScatterPlot(
+    formattedObsArrayForSeriesOnePlusSeriesTwo,
+    phenomenonNamesArr
+  );
 
-  // Order of axes
-  // Y-Axis -- Series 2
-  // X-Axis -- Series 1
+  const CHART_TITLE =
+    createCombinedTextForScatterPlotTitles(phenomenonNamesArr);
 
-  const CHART_TITLE = `${PHENOMENON_NAME_SERIES_2} Versus ${PHENOMENON_NAME_SERIES_1}`;
-  const CHART_SUBTITLE = `Source: ${DATASTREAM_NAME_SERIES_2} & ${DATASTREAM_NAME_SERIES_1}`;
+  const CHART_SUBTITLE = `Sampling rate(s): ${createCombinedTextDelimitedByComma(
+    extractSamplingRateFromDatastreamName(datastreamNamesArr)
+  )}`;
 
-  const SERIES_1_NAME = `${PHENOMENON_NAME_SERIES_1}`;
-  const SERIES_1_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_1}`;
+  const X_AXIS_TITLE = createXAxisTitleTextScatterPlot(
+    phenomenonNamesArr,
+    unitOfMeasurementSymbolsArr
+  );
 
-  const SERIES_2_NAME = `${PHENOMENON_NAME_SERIES_2}`;
-  const SERIES_2_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_2}`;
+  const Y_AXIS_TITLE = createYAxisTitleTextScatterPlot(
+    phenomenonNamesArr,
+    unitOfMeasurementSymbolsArr
+  );
 
-  const SERIES_COMBINED_NAME = "Y, X";
-  const SERIES_COMBINED_SYMBOL_COLOR_RGB_ELEMENTS = "223, 83, 83";
-  const SERIES_COMBINED_SYMBOL_COLOR_OPACITY = ".3";
-  const SERIES_COMBINED_SYMBOL_COLOR = `rgba(${SERIES_COMBINED_SYMBOL_COLOR_RGB_ELEMENTS}, ${SERIES_COMBINED_SYMBOL_COLOR_OPACITY})`;
+  // The unit of measurement symbols for the x-axis is the first element of the array
+  // Assume that we will be comparing similar phenomena, so we can reuse this symbol
+  const UNIT_OF_MEASUREMENT_SYMBOL = unitOfMeasurementSymbolsArr[0];
 
   const MARKER_RADIUS = 2;
 
@@ -273,10 +398,12 @@ const drawScatterPlotHighcharts = function (
 
     title: {
       text: CHART_TITLE,
+      "align": "left",
     },
 
     subtitle: {
       text: CHART_SUBTITLE,
+      "align": "left",
     },
 
     xAxis: {
@@ -285,7 +412,7 @@ const drawScatterPlotHighcharts = function (
       },
       title: {
         enabled: true,
-        text: `${SERIES_1_NAME} [${SERIES_1_SYMBOL}]`,
+        text: X_AXIS_TITLE,
       },
       startOnTick: true,
       endOnTick: true,
@@ -298,7 +425,7 @@ const drawScatterPlotHighcharts = function (
           format: `{value}`,
         },
         title: {
-          text: `${SERIES_2_NAME} [${SERIES_2_SYMBOL}]`,
+          text: Y_AXIS_TITLE,
         },
       },
     ],
@@ -333,22 +460,16 @@ const drawScatterPlotHighcharts = function (
         const headerString = `${this.series.name}<br>`;
         const pointString = `<b>${this.point.y.toFixed(
           2
-        )} ${SERIES_1_SYMBOL}, ${this.point.x.toFixed(
+        )} ${UNIT_OF_MEASUREMENT_SYMBOL}, ${this.point.x.toFixed(
           2
-        )} ${SERIES_2_SYMBOL}</b>`;
+        )} ${UNIT_OF_MEASUREMENT_SYMBOL}</b>`;
         return headerString + pointString;
       },
     },
 
     exporting: chartExportOptions,
 
-    series: [
-      {
-        name: SERIES_COMBINED_NAME,
-        color: SERIES_COMBINED_SYMBOL_COLOR,
-        data: formattedObsArrayForSeriesOnePlusSeriesTwo,
-      },
-    ],
+    series: seriesOptionsArr,
   });
 };
 
diff --git a/public/js/src_modules/fetchData.mjs b/public/js/src_modules/fetchData.mjs
index 718620d2ae827553422df275126cf5931f0c9c5d..0311398ccdcb8ab4e088692357667ff38e95f137 100644
--- a/public/js/src_modules/fetchData.mjs
+++ b/public/js/src_modules/fetchData.mjs
@@ -271,6 +271,7 @@ const getObservationsFromMultipleDatastreams = async function (
 
 /**
  * Retrieve the metadata from a single Datastream or multiple Datastreams and the Observations corresponding to the Datastream(s)
+ * @async
  * @param {String} baseUrl Base URL of the STA server
  * @param {Object} urlParamObj The URL parameters to be sent together with the GET request
  * @param {Array} bldgSensorSamplingRateNestedArr A N*1 array (where N >= 1) containing a nested array of buildings, sensors & sampling rates as strings, i.e. [["101", "rl", "15min"]] or [["101", "rl", "15min"], ["102", "vl", "60min"]] or [["101", "rl", "15min"], ["102", "vl", "60min"], ["225", "vl", "60min"]], etc