diff --git a/public/js/appChart.js b/public/js/appChart.js
index c66a8b8f99a9f641f2b47ad44789c68f86799517..bd1cce104363e98396fd5ca8ca7ffd2fa50e9c81 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -206,6 +206,37 @@ const getMetadataFromMultipleDatastreams = async function (datastreamsUrlArr) {
   }
 };
 
+/**
+ * Match the unitOfMeasurement's string representation of a symbol to an actual symbol, where necessary
+ * @param {String} unitOfMeasurementSymbolString String representation of the unitOfMeasurement's symbol
+ * @returns {String} The unitOfMeasurement's symbol
+ */
+const matchUnitOfMeasurementSymbolStringToSymbol = function (
+  unitOfMeasurementSymbolString
+) {
+  const unicodeCodePointDegreeSymbol = "\u00B0";
+  const unicodeCodePointSuperscriptThree = "\u00B3";
+
+  if (unitOfMeasurementSymbolString === "degC")
+    return `${unicodeCodePointDegreeSymbol}C`;
+
+  if (unitOfMeasurementSymbolString === "m3/h")
+    return `m${unicodeCodePointSuperscriptThree}/h`;
+
+  // If no symbol exists
+  return unitOfMeasurementSymbolString;
+};
+
+/**
+ * Extract the phenomenon name from a Datastream's name
+ * @param {String} datastreamName A string representing the Datastream's name
+ * @returns {String} The extracted phenomenon name
+ */
+const extractPhenomenonNameFromDatastreamName = function (datastreamName) {
+  const regex = /\/ (.*) DS/;
+  return datastreamName.match(regex)[1]; // use second element in array
+};
+
 /**
  * Format the response containing a Datastream's metadata from Sensorthings API
  * @param {Object} datastreamMetadata An object containing a Datastream's metadata
@@ -219,20 +250,13 @@ const formatDatastreamMetadataForChart = function (datastreamMetadata) {
   } = datastreamMetadata;
 
   // Extract phenomenon name from Datastream name
-  const regex = /\/ (.*) DS/;
-  const phenomenonName = datastreamName.match(regex)[1]; // use second element in array
-
-  // Match the unitOfMeasurement's string representation of a symbol
-  // to an actual symbol, where necessary
-  const unitOfMeasurementSymbol = (() => {
-    if (unitOfMeasurement.symbol === "degC") {
-      return "℃";
-    } else if (unitOfMeasurement.symbol === "m3/h") {
-      return "m<sup>3</sup>/h";
-    } else {
-      return unitOfMeasurement.symbol;
-    }
-  })();
+  const phenomenonName =
+    extractPhenomenonNameFromDatastreamName(datastreamName);
+
+  // Get the unitOfMeasurement's symbol
+  const unitOfMeasurementSymbol = matchUnitOfMeasurementSymbolStringToSymbol(
+    unitOfMeasurement.symbol
+  );
 
   return {
     datastreamDescription,
@@ -244,7 +268,7 @@ const formatDatastreamMetadataForChart = function (datastreamMetadata) {
 
 /**
  * Format the response from SensorThings API to make it suitable for heatmap
- * @param {Array} obsArray Response from SensorThings API as array
+ * @param {Array} obsArray Array of observations (timestamp + value) that is response from SensorThings API
  * @returns {Array} Array of formatted observations suitable for use in a heatmap
  */
 const formatSTAResponseForHeatMap = function (obsArray) {
@@ -270,6 +294,28 @@ const formatSTAResponseForHeatMap = function (obsArray) {
   return dataSTAFormatted;
 };
 
+/**
+ * Calculate the minimum and maximum values for a heatmap's color axis
+ * @param {Array} formattedObsArrHeatmap Response from SensorThings API formatted for use in a heatmap
+ * @returns {Object} An object containing the minimum and maximum values
+ */
+const calculateMinMaxValuesForHeatmapColorAxis = function (
+  formattedObsArrHeatmap
+) {
+  // The observation value is the third element in array
+  const obsValueArr = formattedObsArrHeatmap.map((obs) => obs[2]);
+
+  // Extract integer part
+  const minValue = Math.trunc(Math.min(...obsValueArr));
+  const maxValue = Math.trunc(Math.max(...obsValueArr));
+
+  // Calculate the closest multiple of 5
+  const minObsValue = minValue - (minValue % 5);
+  const maxObsValue = maxValue + (5 - (maxValue % 5));
+
+  return { minObsValue, maxObsValue };
+};
+
 /**
  * Draw a heatmap using Highcharts library
  * @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap
@@ -287,24 +333,10 @@ const drawHeatMapHC = function (
     unitOfMeasurementSymbol: PHENOMENON_SYMBOL,
   } = formattedDatastreamMetadata;
 
-  // Function returns the min and max observation values
   const {
     minObsValue: MINIMUM_VALUE_COLOR_AXIS,
     maxObsValue: MAXIMUM_VALUE_COLOR_AXIS,
-  } = (() => {
-    // The observation value is the third element in array
-    const obsValueArr = formattedObsArrayForHeatmap.map((obs) => obs[2]);
-
-    // Extract integer part
-    const minValue = Math.trunc(Math.min(...obsValueArr));
-    const maxValue = Math.trunc(Math.max(...obsValueArr));
-
-    // Calculate the closest multiple of 5
-    const minObsValue = minValue - (minValue % 5);
-    const maxObsValue = maxValue + (5 - (maxValue % 5));
-
-    return { minObsValue, maxObsValue };
-  })();
+  } = calculateMinMaxValuesForHeatmapColorAxis(formattedObsArrayForHeatmap);
 
   Highcharts.chart("chart-heatmap", {
     chart: {
@@ -399,11 +431,11 @@ const drawHeatMapHC = function (
 };
 
 /**
- * Convert the observations' phenomenonTime from an ISO 8601 string to Unix epoch
+ * Format the response from SensorThings API to make it suitable for line chart
  * @param {Array} obsArray Response from SensorThings API as array
- * @returns {Array} Array of formatted observations suitable for use in a line chart or scatter plot
+ * @returns {Array} Array of formatted observations suitable for use in a line chart
  */
-const formatSTAResponseForLineChartOrScatterPlot = function (obsArray) {
+const formatSTAResponseForLineChart = function (obsArray) {
   if (!obsArray) return;
 
   const dataSTAFormatted = obsArray.map((result) => {
@@ -478,7 +510,7 @@ const getSymmetricDifferenceBetweenArrays = function (
     .filter((timestampOne) => !obsTimestampArrayTwo.includes(timestampOne))
     .concat(
       obsTimestampArrayTwo.filter(
-        (timestampTwo) => !obsTimestampArrayTwo.includes(timestampTwo)
+        (timestampTwo) => !obsTimestampArrayOne.includes(timestampTwo)
       )
     );
 
@@ -486,16 +518,16 @@ const getSymmetricDifferenceBetweenArrays = function (
 };
 
 /**
- * Determines the indexes of timestamps that are missing from a smaller set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other
- * @param {Array} missingTimestampsArr An array of strings representing the missing timestamps
- * @param {Array} largerObsTimestampArr An array of timestamps for the larger array of observations
+ * Determines the indexes of timestamps that are unique to the larger set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other
+ * @param {Array} uniqueTimestampsArr An array of timestamps unique to the larger set of observations
+ * @param {Array} largerObsTimestampArr An array of timestamps for the larger set of observations
  * @returns {Array} An array of the indexes of the missing observations
  */
-const getIndexOfMissingObservation = function (
-  missingTimestampsArr,
+const getIndexesOfUniqueObservations = function (
+  uniqueTimestampsArr,
   largerObsTimestampArr
 ) {
-  const indexesMissingObs = missingTimestampsArr.map((index) =>
+  const indexesMissingObs = uniqueTimestampsArr.map((index) =>
     largerObsTimestampArr.indexOf(index)
   );
 
@@ -503,16 +535,19 @@ const getIndexOfMissingObservation = function (
 };
 
 /**
- * Removes observations (by modifying array in place) from a larger set of observations that are missing from a smaller set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other
- * @param {Array} missingIndexesArr An array of the indexes of the observations missing from the smaller set of observations
+ * Removes observations (by modifying array in place) that are unique to a larger set of observations. Based on the comparison of two observation arrays, where one array is larger than the other
+ * @param {Array} uniqueIndexesArr An array of the indexes unique to the larger set of observations
  * @param {Array} largerObsArr The larger array of observations (timestamp + value) which is modified in place
  * @returns {undefined}
  */
-const removeMissingObservationFromLargerArray = function (
-  missingIndexesArr,
+const removeUniqueObservationsFromLargerArray = function (
+  uniqueIndexesArr,
   largerObsArr
 ) {
-  missingIndexesArr.forEach((index) => {
+  // Reverse the indexes array so that the larger index is removed first
+  uniqueIndexesArr.reverse();
+
+  uniqueIndexesArr.forEach((index) => {
     if (index > -1) {
       largerObsArr.splice(index, 1);
     }
@@ -533,6 +568,98 @@ const getLargerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
   if (firstArr.length < secondArr.length) return secondArr;
 };
 
+/**
+ * Compares the length of two input arrays to determine the smaller one
+ * @param {Array} firstArr First input array
+ * @param {Array} secondArr Second input array
+ * @returns {Array} The smaller array
+ */
+const getSmallerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
+  if (firstArr.length === secondArr.length) return;
+
+  if (firstArr.length < secondArr.length) return firstArr;
+
+  if (firstArr.length > secondArr.length) return secondArr;
+};
+
+/**
+ * Utility function for deleting the unique observations from a larger array
+ * @param {Array} obsArrayOne Array of observations (timestamp + value) that is response from SensorThings API
+ * @param {Array} obsArrayTwo Array of observations (timestamp + value) that is response from SensorThings API
+ * @returns {Array} Two arrays of observations (timestamp + value) with matching timestamps and equal lengths
+ */
+const deleteUniqueObservationsFromLargerArray = function (
+  obsArrayOne,
+  obsArrayTwo
+) {
+  // Create arrays with timestamps only
+  const obsArrayOneTimestamp = obsArrayOne.map(
+    (obsTimeValue) => obsTimeValue[0]
+  );
+  const obsArrayTwoTimestamp = obsArrayTwo.map(
+    (obsTimeValue) => obsTimeValue[0]
+  );
+
+  const missingTimestamp = getSymmetricDifferenceBetweenArrays(
+    obsArrayOneTimestamp,
+    obsArrayTwoTimestamp
+  );
+
+  // Determine the larger observation timestamp array
+  const biggerObsTimestampArr = getLargerArrayBetweenTwoInputArrays(
+    obsArrayOneTimestamp,
+    obsArrayTwoTimestamp
+  );
+
+  // Indexes of the missing observations
+  const indexesMissingObsArr = getIndexesOfUniqueObservations(
+    missingTimestamp,
+    biggerObsTimestampArr
+  );
+
+  // Determine the larger observation array
+  const biggerObsArr = getLargerArrayBetweenTwoInputArrays(
+    obsArrayOne,
+    obsArrayTwo
+  );
+
+  // Determine the smaller observation array
+  const smallerObsArr = getSmallerArrayBetweenTwoInputArrays(
+    obsArrayOne,
+    obsArrayTwo
+  );
+
+  // Remove the missing observation from the larger array of observations
+  // Modifies the array in place
+  removeUniqueObservationsFromLargerArray(indexesMissingObsArr, biggerObsArr);
+
+  return [biggerObsArr, smallerObsArr];
+};
+
+/**
+ * Utility function for deleting the unique observations from a larger array AND ensuring the order of input arrays is maintained
+ * @param {Array} obsArrayOne Array of observations (timestamp + value) that is response from SensorThings API
+ * @param {Array} obsArrayTwo Array of observations (timestamp + value) that is response from SensorThings API
+ * @returns {Array} Two arrays of observations (timestamp + value) with matching timestamps and equal lengths
+ */
+const checkForAndDeleteUniqueObservationsFromLargerArray = function (
+  obsArrayOne,
+  obsArrayTwo
+) {
+  if (obsArrayOne.length === obsArrayTwo.length) return;
+
+  //   Case 1: obsArrayOne.length < obsArrayTwo.length
+  if (obsArrayOne.length < obsArrayTwo.length) {
+    const [biggerObsArr, smallerObsArr] =
+      deleteUniqueObservationsFromLargerArray(obsArrayOne, obsArrayTwo);
+
+    return [smallerObsArr, biggerObsArr];
+  }
+
+  //   Case 2: obsArrayOne.length > obsArrayTwo.length
+  return deleteUniqueObservationsFromLargerArray(obsArrayOne, obsArrayTwo);
+};
+
 /**
  * Extracts and combines observation values from two imput observation arrays of equal length
  * @param {Array} obsArrayOne First set of N observations (timestamp + value)
@@ -554,49 +681,20 @@ const createCombinedObservationValues = function (obsArrayOne, obsArrayTwo) {
 
 /**
  * Format the response from SensorThings API to make it suitable for scatter plot
- * @param {Array} obsArrayOne Response from SensorThings API as array
- * @param {Array} obsArrayTwo Response from SensorThings API as array
+ * @param {Array} obsArrayOne Array of observations (timestamp + value) that is response from SensorThings API
+ * @param {Array} obsArrayTwo Array of observations (timestamp + value) that is response from SensorThings API
  * @returns {Array} Array of formatted observations suitable for use in a scatter plot
  */
 const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
   // When our observation arrays have DIFFERENT lengths
   if (obsArrayOne.length !== obsArrayTwo.length) {
-    // Create arrays with timestamps only
-    const obsArrayOneTimestamp = obsArrayOne.map(
-      (obsTimeValue) => obsTimeValue[0]
-    );
-    const obsArrayTwoTimestamp = obsArrayTwo.map(
-      (obsTimeValue) => obsTimeValue[0]
-    );
-
-    const missingTimestamp = getSymmetricDifferenceBetweenArrays(
-      obsArrayOneTimestamp,
-      obsArrayTwoTimestamp
-    );
-
-    // Determine the larger observation timestamp array
-    const biggerObsTimestampArr = getLargerArrayBetweenTwoInputArrays(
-      obsArrayOneTimestamp,
-      obsArrayTwoTimestamp
-    );
-
-    // Indexes of the missing observations
-    const indexesMissingObsArr = getIndexOfMissingObservation(
-      missingTimestamp,
-      biggerObsTimestampArr
-    );
-
-    // Determine the larger observation array
-    const biggerObsArr = getLargerArrayBetweenTwoInputArrays(
-      obsArrayOne,
-      obsArrayTwo
-    );
-
-    // Remove the missing observation from the larger array of observations
-    // Modifies the array in place
-    removeMissingObservationFromLargerArray(indexesMissingObsArr, biggerObsArr);
+    const [obsArrayOneFinal, obsArrayTwoFinal] =
+      checkForAndDeleteUniqueObservationsFromLargerArray(
+        obsArrayOne,
+        obsArrayTwo
+      );
 
-    return createCombinedObservationValues(obsArrayOne, obsArrayTwo);
+    return createCombinedObservationValues(obsArrayOneFinal, obsArrayTwoFinal);
   }
 
   // When our observation arrays already have SAME lengths
@@ -892,6 +990,93 @@ const getMetadataPlusObservationsFromMultipleDatastreams = async function (
   }
 };
 
+/**
+ * Calculates the temperature difference, dT, between Vorlauf temperature [VL] and Rücklauf temperature [RL] (i.e., dT = VL - RL)
+ * @async
+ * @param {String} buildingId The building ID as a string
+ * @param {String} samplingRate The sampling rate as a string
+ * @returns A promise that contains an array (that is made up of a temperature difference array and a metadata object) when fulfilled
+ */
+const calculateVorlaufMinusRuecklaufTemperature = async function (
+  buildingId,
+  samplingRate
+) {
+  const bldgSensorSamplingRateArr = [
+    [buildingId, "vl", samplingRate],
+    [buildingId, "rl", samplingRate],
+  ];
+
+  const BUILDING_ID = buildingId;
+  const SAMPLING_RATE = samplingRate;
+
+  const observationsPlusMetadata =
+    await getMetadataPlusObservationsFromMultipleDatastreams(
+      bldgSensorSamplingRateArr
+    );
+
+  // Extract Vorlauf temperature and Ruecklauf temperature
+  const [[vorlaufTemp, ruecklaufTemp], [metadataVorlauf, metadataRuecklauf]] =
+    observationsPlusMetadata;
+
+  const vorlaufTempValues = vorlaufTemp.map((obs) => obs[1]);
+  const ruecklaufTempValues = ruecklaufTemp.map((obs) => obs[1]);
+
+  // The arrays have equal length, we need only use one of them for looping
+  // Resulting array contains the following pairs (timestamp + dT)
+  const vorlaufMinusRuecklaufTemp = vorlaufTemp.map((obs, i) => [
+    obs[0],
+    vorlaufTempValues[i] - ruecklaufTempValues[i],
+  ]);
+
+  // From Vorlauf metadata, extract `name` and `unitOfMeasurement`
+  const { name: datastreamNameVorlauf, unitOfMeasurement } = metadataVorlauf;
+
+  // From Ruecklauf metadata, extract `name`
+  const { name: datastreamNameRuecklauf } = metadataRuecklauf;
+
+  // Extract the phenomenon names from Datastream names
+  const phenomenonNameVorlauf = extractPhenomenonNameFromDatastreamName(
+    datastreamNameVorlauf
+  );
+  const phenomenonNameRuecklauf = extractPhenomenonNameFromDatastreamName(
+    datastreamNameRuecklauf
+  );
+
+  // Create our custom datastream description text
+  const descriptionCombined = `Computed dT: ${phenomenonNameVorlauf} minus ${phenomenonNameRuecklauf}`;
+
+  // The resulting datastream description string has two `temperature` substrings;
+  // replace the first occurence with an empty string
+  const description = descriptionCombined.replace("temperature", "");
+
+  // Create our custom datastream name text
+  const name = `BOSCH_${BUILDING_ID} / dT Temperature difference (VL-RL) DS:${SAMPLING_RATE}`;
+
+  return [
+    vorlaufMinusRuecklaufTemp,
+
+    // The datastream metadata object needs to have these property names
+    {
+      description,
+      name,
+      unitOfMeasurement,
+    },
+  ];
+};
+
+/**
+ * Test plotting of temp difference (dT) using heatmap
+ */
+const drawHeatmapHCUsingTempDifference = async function () {
+  const [tempDifferenceObsArrBau225, tempDifferenceMetadataBau225] =
+    await calculateVorlaufMinusRuecklaufTemperature("225", "60min");
+
+  drawHeatMapHC(
+    formatSTAResponseForHeatMap(tempDifferenceObsArrBau225),
+    formatDatastreamMetadataForChart(tempDifferenceMetadataBau225)
+  );
+};
+
 /**
  * Test drawing of scatter plot chart
  */
@@ -922,7 +1107,8 @@ const drawScatterPlotHCTest2 = async function () {
 };
 
 (async () => {
-  await drawScatterPlotHCTest2();
+  // await drawScatterPlotHCTest2();
+  await drawHeatmapHCUsingTempDifference();
 })();
 
 export {
@@ -937,7 +1123,7 @@ export {
   formatDatastreamMetadataForChart,
   formatSTAResponseForHeatMap,
   drawHeatMapHC,
-  formatSTAResponseForLineChartOrScatterPlot,
+  formatSTAResponseForLineChart,
   drawLineChartHC,
   getCombinedObservationsFromAllNextLinks,
   getMetadataPlusObservationsFromSingleDatastream,
diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js
index ac3a750c2a202da17d00e82360584771320082ca..52817635b23a9d984a60d47cee77e0bb6abc4590 100644
--- a/public/js/dropDownList.js
+++ b/public/js/dropDownList.js
@@ -11,7 +11,7 @@ import {
   formatDatastreamMetadataForChart,
   formatSTAResponseForHeatMap,
   drawHeatMapHC,
-  formatSTAResponseForLineChartOrScatterPlot,
+  formatSTAResponseForLineChart,
   drawLineChartHC,
   getCombinedObservationsFromAllNextLinks,
   getMetadataPlusObservationsFromSingleDatastream,
@@ -308,7 +308,7 @@ const selectChartTypeFromDropDown = async function () {
 
     if (selectedChartType === "Line") {
       drawLineChartHC(
-        formatSTAResponseForLineChartOrScatterPlot(combinedObs),
+        formatSTAResponseForLineChart(combinedObs),
         formatDatastreamMetadataForChart(datastreamMetadata)
       );
     } else if (selectedChartType === "Heatmap") {