diff --git a/public/js/appChart.js b/public/js/appChart.js
index 72fdec508f2ff9db8a12c3acbd2dde4fb04b85b7..4a49952384c662bd202b7b5da1951d7b21867f3b 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -41,6 +41,8 @@ import {
 } from "./src_modules/aggregateHelpers.mjs";
 
 import {
+  calculateMinimumObservationValuesWithinInterval,
+  calculateMaximumObservationValuesWithinInterval,
   calculateSumOfObservationValuesWithinInterval,
   calculateAverageOfObservationValuesWithinInterval,
 } from "./src_modules/aggregate.mjs";
@@ -585,6 +587,176 @@ const drawLineChartDailyAverageTest = async function () {
   }
 };
 
+/**
+ * Test drawing of line chart using aggregation / minimum result - daily
+ */
+const drawLineChartDailyMinTest = 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 average of values of observations - daily
+    const observationsMinimumDailyNestedArr =
+      calculateMinimumObservationValuesWithinInterval(
+        observationsNestedArr,
+        "60min",
+        uniqueCalendarDatesNestedArr,
+        "daily"
+      );
+
+    // Format the observations - daily
+    const formattedObservationsMinimumDailyNestedArr =
+      observationsMinimumDailyNestedArr.map((obsMinDailyArr, i) =>
+        formatAggregationResultForColumnChart(
+          uniqueCalendarDatesNestedArr[i],
+          obsMinDailyArr
+        )
+      );
+
+    // Format the metadata
+    const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
+      formatDatastreamMetadataForChart(metadataObj)
+    );
+
+    // Extract the formatted metadata properties
+    const extractedFormattedDatastreamProperties =
+      extractPropertiesFromFormattedDatastreamMetadata(
+        formattedMetadataNestedArr,
+        true,
+        "daily",
+        "minimum"
+      );
+
+    drawLineChartHighcharts(
+      formattedObservationsMinimumDailyNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } catch (err) {
+    console.error(err);
+  }
+};
+
+/**
+ * Test drawing of line chart using aggregation / maximum result - daily
+ */
+const drawLineChartMonthlyMaxTest = 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-12-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 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
+    );
+  } catch (err) {
+    console.error(err);
+  }
+};
+
 // drawScatterPlotHCTest2();
 // drawHeatmapHCUsingTempDifference();
 // testLineChartMultipleSeries();
@@ -593,3 +765,5 @@ const drawLineChartDailyAverageTest = async function () {
 // drawColumnChartNonAggregationTest();
 // drawLineChartMonthlyAverageTest();
 // drawLineChartDailyAverageTest();
+// drawLineChartDailyMinTest();
+// drawLineChartMonthlyMaxTest();
diff --git a/public/js/src_modules/aggregate.mjs b/public/js/src_modules/aggregate.mjs
index 6691c1c3f1838999a8ae84d78277a94ae46df64a..324716daf646328d6ac8a25ef40ec38a9a799b5a 100644
--- a/public/js/src_modules/aggregate.mjs
+++ b/public/js/src_modules/aggregate.mjs
@@ -5,6 +5,28 @@ import {
   extractObservationValuesWithinMonthInterval,
 } from "./aggregateHelpers.mjs";
 
+/**
+ * Calculate the minimum observation values that fall within a time interval delimited by a start date and end date
+ * @param {Array} obsValuesForDaysIntervalArr An array of observation values that fall within our time interval
+ * @returns {Number} A floating-point number representing the minimum of observation values
+ */
+const calculateMinimumObservationValuesWithinDatesInterval = function (
+  obsValuesForDaysIntervalArr
+) {
+  return Math.min(...obsValuesForDaysIntervalArr);
+};
+
+/**
+ * Calculate the maximum observation values that fall within a time interval delimited by a start date and end date
+ * @param {Array} obsValuesForDaysIntervalArr An array of observation values that fall within our time interval
+ * @returns {Number} A floating-point number representing the maximum of observation values
+ */
+const calculateMaximumObservationValuesWithinDatesInterval = function (
+  obsValuesForDaysIntervalArr
+) {
+  return Math.max(...obsValuesForDaysIntervalArr);
+};
+
 /**
  * Calculate the sum of observation values that fall within a time interval delimited by a start date and end date
  * @param {Array} obsValuesForDaysIntervalArr An array of observation values that fall within our time interval
@@ -33,6 +55,28 @@ const calculateAverageOfObservationValuesWithinDatesInterval = function (
   );
 };
 
+/**
+ * Calculate the minimum 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
+ * @returns {Number} A floating-point number representing the minimum of observation values within one calendar month
+ */
+const calculateMinimumObservationValuesWithinMonthInterval = function (
+  obsValuesForMonthIntervalArr
+) {
+  return Math.min(...obsValuesForMonthIntervalArr);
+};
+
+/**
+ * Calculate the maximum 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
+ * @returns {Number} A floating-point number representing the maximum of observation values within one calendar month
+ */
+const calculateMaximumObservationValuesWithinMonthInterval = function (
+  obsValuesForMonthIntervalArr
+) {
+  return Math.max(...obsValuesForMonthIntervalArr);
+};
+
 /**
  * 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
@@ -61,6 +105,102 @@ const calculateAverageOfObservationValuesWithinMonthInterval = function (
   );
 };
 
+/**
+ * Calculate the minimum of observation values within a time interval delimited by a start date and end date. The time interval may be daily or monthly
+ * @param {Array} obsNestedArr A 1*N array that contains N nested arrays of observations (timestamp + value)
+ * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
+ * @param {Array} uniqueCalendarDatesOrMonthsArr A 1*N array of unique calendar dates or calendar months strings
+ * @param {String} aggregationInterval The aggregation interval as a string e.g. "daily", "monthly"
+ * @returns {Array} A 1*N array that contains N nested arrays of minimum values
+ */
+const calculateMinimumObservationValuesWithinInterval = function (
+  obsNestedArr,
+  samplingRate,
+  uniqueCalendarDatesOrMonthsArr,
+  aggregationInterval
+) {
+  // Calculate minimum values of observations - daily
+  // Note the use of the two nested `map` methods
+  if (aggregationInterval === "daily") {
+    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarDatesArr, i) =>
+      uniqueCalendarDatesArr.map((uniqueCalendarDate) =>
+        calculateMinimumObservationValuesWithinDatesInterval(
+          extractObservationValuesWithinDatesInterval(
+            obsNestedArr[i],
+            samplingRate,
+            uniqueCalendarDate,
+            uniqueCalendarDate
+          )
+        )
+      )
+    );
+  }
+
+  // Calculate minimum values of observations - monthly
+  // Note the use of the two nested `map` methods
+  if (aggregationInterval === "monthly") {
+    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarMonthsArr, i) =>
+      uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
+        calculateMinimumObservationValuesWithinMonthInterval(
+          extractObservationValuesWithinMonthInterval(
+            obsNestedArr[i],
+            samplingRate,
+            uniqueCalendarMonth
+          )
+        )
+      )
+    );
+  }
+};
+
+/**
+ * Calculate the maximum of observation values within a time interval delimited by a start date and end date. The time interval may be daily or monthly
+ * @param {Array} obsNestedArr A 1*N array that contains N nested arrays of observations (timestamp + value)
+ * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
+ * @param {Array} uniqueCalendarDatesOrMonthsArr A 1*N array of unique calendar dates or calendar months strings
+ * @param {String} aggregationInterval The aggregation interval as a string e.g. "daily", "monthly"
+ * @returns {Array} A 1*N array that contains N nested arrays of maximum values
+ */
+const calculateMaximumObservationValuesWithinInterval = function (
+  obsNestedArr,
+  samplingRate,
+  uniqueCalendarDatesOrMonthsArr,
+  aggregationInterval
+) {
+  // Calculate maximum values of observations - daily
+  // Note the use of the two nested `map` methods
+  if (aggregationInterval === "daily") {
+    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarDatesArr, i) =>
+      uniqueCalendarDatesArr.map((uniqueCalendarDate) =>
+        calculateMaximumObservationValuesWithinDatesInterval(
+          extractObservationValuesWithinDatesInterval(
+            obsNestedArr[i],
+            samplingRate,
+            uniqueCalendarDate,
+            uniqueCalendarDate
+          )
+        )
+      )
+    );
+  }
+
+  // Calculate maximum values of observations - monthly
+  // Note the use of the two nested `map` methods
+  if (aggregationInterval === "monthly") {
+    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarMonthsArr, i) =>
+      uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
+        calculateMaximumObservationValuesWithinMonthInterval(
+          extractObservationValuesWithinMonthInterval(
+            obsNestedArr[i],
+            samplingRate,
+            uniqueCalendarMonth
+          )
+        )
+      )
+    );
+  }
+};
+
 /**
  * Calculate the sum of observation values within a time interval delimited by a start date and end date. The time interval may be daily or monthly
  * @param {Array} obsNestedArr A 1*N array that contains N nested arrays of observations (timestamp + value)
@@ -158,6 +298,8 @@ const calculateAverageOfObservationValuesWithinInterval = function (
 };
 
 export {
+  calculateMinimumObservationValuesWithinInterval,
+  calculateMaximumObservationValuesWithinInterval,
   calculateSumOfObservationValuesWithinInterval,
   calculateAverageOfObservationValuesWithinInterval,
 };
diff --git a/public/js/src_modules/fetchedDataProcessing.mjs b/public/js/src_modules/fetchedDataProcessing.mjs
index 7d40960c39fe67715eefb321d248bd7dfbc36435..ec6ee9206bd784e6b28e9adbbc4d320c1adc3cb1 100644
--- a/public/js/src_modules/fetchedDataProcessing.mjs
+++ b/public/js/src_modules/fetchedDataProcessing.mjs
@@ -119,11 +119,13 @@ const extractPropertiesFromFormattedDatastreamMetadata = function (
 
   if (
     isMetadataForAggregation &&
+    aggregationType !== "minimum" &&
+    aggregationType !== "maximum" &&
     aggregationType !== "sum" &&
     aggregationType !== "average"
   )
     throw new Error(
-      `The supported aggegation type strings are "sum" or "average"`
+      `The supported aggegation type strings are "minimum", "maximum", "sum" or "average"`
     );
 
   // Create arrays from the properties of the formatted datastream metadata