From f000787d01afcaba244e712f3e08f2adc5e629ec Mon Sep 17 00:00:00 2001
From: Pithon Kabiro <pithon.kabiro@hft-stuttgart.de>
Date: Fri, 24 Sep 2021 13:35:51 +0200
Subject: [PATCH] Use raw and aggregated data when drawing charts

---
 public/js/appChart.js                         |  79 +++++++++++---
 public/js/dropDownList.js                     |   8 +-
 .../src_modules/calculateTemperatureDiff.mjs  |   2 +-
 public/js/src_modules/chartColumn.mjs         | 101 +++++-------------
 public/js/src_modules/chartHelpers.mjs        |  74 +++++++++++++
 public/js/src_modules/chartLine.mjs           |  65 ++++++++---
 public/js/src_modules/chartScatterPlot.mjs    |   9 +-
 ...aProcess.mjs => fetchedDataProcessing.mjs} |  27 +----
 8 files changed, 234 insertions(+), 131 deletions(-)
 rename public/js/src_modules/{fetchedDataProcess.mjs => fetchedDataProcessing.mjs} (87%)

diff --git a/public/js/appChart.js b/public/js/appChart.js
index 7e2cc39..72fdec5 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -6,7 +6,7 @@ import {
 } from "./src_modules/baseUrlPlusQueryParams.mjs";
 
 import {
-  formatSensorThingsApiResponseForLineChart,
+  formatSensorThingsApiResponseForLineOrColumnChart,
   drawLineChartHighcharts,
 } from "./src_modules/chartLine.mjs";
 
@@ -30,7 +30,7 @@ import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./sr
 import {
   formatDatastreamMetadataForChart,
   extractPropertiesFromFormattedDatastreamMetadata,
-} from "./src_modules/fetchedDataProcess.mjs";
+} from "./src_modules/fetchedDataProcessing.mjs";
 
 import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs";
 
@@ -74,7 +74,7 @@ const drawHeatmapHCUsingTempDifference = async function () {
     // Format the metadata
     const formattedTempDiff225MetadataNestedArr =
       metadataTemperatureDiff225NestedArr.map((metadataObj) =>
-        formatDatastreamMetadataForChart(metadataObj, false)
+        formatDatastreamMetadataForChart(metadataObj)
       );
 
     // Extract the formatted metadata properties
@@ -173,12 +173,12 @@ const testLineChartMultipleSeries = async function () {
     // Format the observations
     const formattedObservationsNestedArr = observationsNestedArr.map(
       (observationsArr) =>
-        formatSensorThingsApiResponseForLineChart(observationsArr)
+        formatSensorThingsApiResponseForLineOrColumnChart(observationsArr)
     );
 
     // Format the metadata
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) =>
-      formatDatastreamMetadataForChart(metadataArr, false)
+      formatDatastreamMetadataForChart(metadataArr)
     );
 
     // Extract the formatted metadata properties
@@ -264,7 +264,7 @@ const drawColumnChartMonthlySumTest = async function () {
 
     // Format the metadata
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj, true)
+      formatDatastreamMetadataForChart(metadataObj)
     );
 
     // Extract the formatted metadata properties
@@ -346,7 +346,7 @@ const drawColumnChartDailySumTest = async function () {
 
     // Format the metadata
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj, true)
+      formatDatastreamMetadataForChart(metadataObj)
     );
 
     // Extract the formatted metadata properties
@@ -367,6 +367,54 @@ const drawColumnChartDailySumTest = async function () {
   }
 };
 
+/**
+ * Test drawing of column chart using raw observations
+ */
+const drawColumnChartNonAggregationTest = async function () {
+  try {
+    const sensorsOfInterestNestedArr = [
+      ["125", "vl", "60min"],
+      ["225", "vl", "60min"],
+    ];
+
+    const observationsPlusMetadata =
+      await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
+        BASE_URL,
+        QUERY_PARAMS_COMBINED,
+        sensorsOfInterestNestedArr
+      );
+
+    // Extract the observations and metadata for each sensor
+    // Array elements in same order as input array
+    const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
+
+    // Format the observations
+    const formattedObservationsNestedArr = observationsNestedArr.map(
+      (observationsArr) =>
+        formatSensorThingsApiResponseForLineOrColumnChart(observationsArr)
+    );
+
+    // Format the metadata
+    const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) =>
+      formatDatastreamMetadataForChart(metadataArr)
+    );
+
+    // Extract the formatted metadata properties
+    const extractedFormattedDatastreamProperties =
+      extractPropertiesFromFormattedDatastreamMetadata(
+        formattedMetadataNestedArr,
+        false
+      );
+
+    drawColumnChartHighcharts(
+      formattedObservationsNestedArr,
+      extractedFormattedDatastreamProperties
+    );
+  } catch (err) {
+    console.error(err);
+  }
+};
+
 /**
  * Test drawing of line chart using aggregation / average result - monthly
  */
@@ -433,17 +481,17 @@ const drawLineChartMonthlyAverageTest = async function () {
       );
 
     // Format the metadata
-    // NOTE: we use the `false` flag here because line charts are meant to work with non-aggregated data
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj, false)
+      formatDatastreamMetadataForChart(metadataObj)
     );
 
     // Extract the formatted metadata properties
-    // NOTE: we use the `false` flag here because line charts are meant to work with non-aggregated data
     const extractedFormattedDatastreamProperties =
       extractPropertiesFromFormattedDatastreamMetadata(
         formattedMetadataNestedArr,
-        false
+        true,
+        "monthly",
+        "average"
       );
 
     drawLineChartHighcharts(
@@ -515,17 +563,17 @@ const drawLineChartDailyAverageTest = async function () {
       );
 
     // Format the metadata
-    // NOTE: we use the `false` flag here because line charts are meant to work with non-aggregated data
     const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj, false)
+      formatDatastreamMetadataForChart(metadataObj)
     );
 
     // Extract the formatted metadata properties
-    // NOTE: we use the `false` flag here because line charts are meant to work with non-aggregated data
     const extractedFormattedDatastreamProperties =
       extractPropertiesFromFormattedDatastreamMetadata(
         formattedMetadataNestedArr,
-        false
+        true,
+        "daily",
+        "average"
       );
 
     drawLineChartHighcharts(
@@ -542,5 +590,6 @@ const drawLineChartDailyAverageTest = async function () {
 // testLineChartMultipleSeries();
 // drawColumnChartMonthlySumTest();
 // drawColumnChartDailySumTest();
+// drawColumnChartNonAggregationTest();
 // drawLineChartMonthlyAverageTest();
 // drawLineChartDailyAverageTest();
diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js
index c765c8b..815c337 100644
--- a/public/js/dropDownList.js
+++ b/public/js/dropDownList.js
@@ -10,10 +10,10 @@ import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./sr
 import {
   formatDatastreamMetadataForChart,
   extractPropertiesFromFormattedDatastreamMetadata,
-} from "./src_modules/fetchedDataProcess.mjs";
+} from "./src_modules/fetchedDataProcessing.mjs";
 
 import {
-  formatSensorThingsApiResponseForLineChart,
+  formatSensorThingsApiResponseForLineOrColumnChart,
   drawLineChartHighcharts,
 } from "./src_modules/chartLine.mjs";
 
@@ -282,7 +282,7 @@ const selectChartTypeFromDropDown = async function () {
     // Create formatted array(s) for observations - line chart
     const formattedObsLineChartNestedArr = observationsNestedArr.map(
       (observationsArr) =>
-        formatSensorThingsApiResponseForLineChart(observationsArr)
+        formatSensorThingsApiResponseForLineOrColumnChart(observationsArr)
     );
 
     // Create formatted array(s) for observations - heatmap
@@ -293,7 +293,7 @@ const selectChartTypeFromDropDown = async function () {
 
     // Create formatted array(s) for metadata - same for both chart types
     const formattedMetadataArr = metadataNestedArr.map((metadataObj) =>
-      formatDatastreamMetadataForChart(metadataObj, false)
+      formatDatastreamMetadataForChart(metadataObj)
     );
 
     // Extract the formatted metadata properties
diff --git a/public/js/src_modules/calculateTemperatureDiff.mjs b/public/js/src_modules/calculateTemperatureDiff.mjs
index 157ae45..2922528 100644
--- a/public/js/src_modules/calculateTemperatureDiff.mjs
+++ b/public/js/src_modules/calculateTemperatureDiff.mjs
@@ -2,7 +2,7 @@
 
 import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./fetchData.mjs";
 
-import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcess.mjs";
+import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcessing.mjs";
 
 /**
  * Calculate the temperature difference, dT, between Vorlauf temperature [VL] and RÞcklauf temperature [RL] (i.e., dT = VL - RL)
diff --git a/public/js/src_modules/chartColumn.mjs b/public/js/src_modules/chartColumn.mjs
index cb0b39e..4c89ebc 100644
--- a/public/js/src_modules/chartColumn.mjs
+++ b/public/js/src_modules/chartColumn.mjs
@@ -3,11 +3,11 @@
 import {
   chartExportOptions,
   createCombinedTextDelimitedByComma,
+  createFullTitleForLineOrColumnChart,
+  createTooltipDateString,
   extractSamplingRateFromDatastreamName,
 } from "./chartHelpers.mjs";
 
-import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcess.mjs";
-
 /**
  * Format a computed aggregation result to make it suitable for a column chart
  * @param {Array} calendarDatesMonthsStrArr An array of unique calendar dates strings (in "YYYY-MM-DD" fromat) or unique calendar months strings (in "YYYY-MM" format)
@@ -65,64 +65,6 @@ const createSeriesOptionsForColumnChart = function (
   );
 };
 
-/**
- * Creates a date string that is used as a header for a shared tooltip string for a column chart
- * @param {Number} pointXAxisValue The x-axis value (Unix timestamp) which is common for a set of data points
- * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
- * @returns {String} A calendar date or calendar month string that is common for a set of data points
- */
-const createHeaderDateString = function (pointXAxisValue, aggregationInterval) {
-  if (aggregationInterval === "daily")
-    return `${Highcharts.dateFormat("%A, %b %e, %Y", pointXAxisValue)}`;
-  else if (aggregationInterval === "monthly")
-    return `${Highcharts.dateFormat("%b %Y", pointXAxisValue)}`;
-};
-
-/**
- * Create a partial string for a column's chart title
- * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
- * @param {String} aggregationType The aggregation type as a string, either "sum" or "average"
- * @returns {String} Partial string for chart title
- */
-const createPartialTitleForColumnChart = function (
-  aggregationInterval,
-  aggregationType
-) {
-  // Case 1: No aggregation; return empty string
-  if (!aggregationInterval && !aggregationType) return ``;
-
-  // Case 2: Aggregation; capitalize the first characters
-  return `${
-    aggregationInterval.slice(0, 1).toUpperCase() + aggregationInterval.slice(1)
-  } ${aggregationType.slice(0, 1).toUpperCase() + aggregationType.slice(1)}`;
-};
-
-/**
- * Create a full string for a column's chart title
- * @param {Array} datastreamNamesArr An array of datastream names as strings
- * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
- * @param {String} aggregationType The aggregation type as a string, either "sum" or "average"
- * @returns {String} Full string for chart title
- */
-const createFullTitleForColumnChart = function (
-  phenomenonNamesArr,
-  aggregationInterval,
-  aggregationType
-) {
-  // Case 1: No aggregation; create a comma separated string of phenomenon names
-  if (!aggregationInterval && !aggregationType)
-    return `${createPartialTitleForColumnChart(
-      aggregationInterval,
-      aggregationType
-    )}${createCombinedTextDelimitedByComma(phenomenonNamesArr)}`;
-
-  // Case 2: Aggregation
-  return `${createPartialTitleForColumnChart(
-    aggregationInterval,
-    aggregationType
-  )} ${phenomenonNamesArr[0]}`;
-};
-
 /**
  * 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
@@ -133,19 +75,34 @@ const drawColumnChartHighcharts = function (
   formattedAggResultArraysForColumnChart,
   extractedFormattedDatastreamProperties
 ) {
-  // Arrays of datastream properties
-  const {
-    datastreamNamesArr,
+  // Formatted datastream properties
+  let datastreamNamesArr,
+    phenomenonNamesArr,
     buildingIdsPhenomenonNamesArr,
     unitOfMeasurementSymbolsArr,
     aggregationInterval,
-    aggregationType,
-  } = extractedFormattedDatastreamProperties;
-
-  // Create an array of phenomenon names
-  const phenomenonNamesArr = datastreamNamesArr.map((datastreamName) =>
-    extractPhenomenonNameFromDatastreamName(datastreamName)
-  );
+    aggregationType;
+
+  // Check whether the datastream properties are for aggregated observations
+  if (extractedFormattedDatastreamProperties?.aggregationType === undefined) {
+    // Case 1: No aggregation
+    ({
+      datastreamNamesArr,
+      phenomenonNamesArr,
+      buildingIdsPhenomenonNamesArr,
+      unitOfMeasurementSymbolsArr,
+    } = extractedFormattedDatastreamProperties);
+  } else {
+    // Case 2: Aggregation
+    ({
+      datastreamNamesArr,
+      phenomenonNamesArr,
+      buildingIdsPhenomenonNamesArr,
+      unitOfMeasurementSymbolsArr,
+      aggregationInterval,
+      aggregationType,
+    } = extractedFormattedDatastreamProperties);
+  }
 
   // Create the array of series options object(s)
   const seriesOptionsArr = createSeriesOptionsForColumnChart(
@@ -159,7 +116,7 @@ const drawColumnChartHighcharts = function (
   // Assume that we will be comparing similar phenomena, so we can use the first phenomenon symbol
   const unitOfMeasurementSymbol = unitOfMeasurementSymbolsArr[0];
 
-  const textChartTitle = createFullTitleForColumnChart(
+  const textChartTitle = createFullTitleForLineOrColumnChart(
     phenomenonNamesArr,
     aggregationInterval,
     aggregationType
@@ -209,7 +166,7 @@ const drawColumnChartHighcharts = function (
             }</span>: <b>${point.y.toFixed(2)} ${
               unitOfMeasurementSymbolsArr[i]
             }</b>`,
-          `${createHeaderDateString(this.x, aggregationInterval)}`
+          `${createTooltipDateString(this.x, aggregationInterval)}`
         );
       },
       shared: true,
diff --git a/public/js/src_modules/chartHelpers.mjs b/public/js/src_modules/chartHelpers.mjs
index 10f3d51..5b61a42 100644
--- a/public/js/src_modules/chartHelpers.mjs
+++ b/public/js/src_modules/chartHelpers.mjs
@@ -57,6 +57,68 @@ const createCombinedTextDelimitedByComma = function (metadataPropertiesArr) {
   return metadataPropertiesArr.join(", ");
 };
 
+/**
+ * Create a partial string for a line chart or column chart title
+ * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
+ * @param {String} aggregationType The aggregation type as a string, either "sum" or "average"
+ * @returns {String} Partial string for chart title
+ */
+const createPartialTitleForLineOrColumnChart = function (
+  aggregationInterval,
+  aggregationType
+) {
+  // Case 1: No aggregation; return empty string
+  if (!aggregationInterval && !aggregationType) return ``;
+
+  // Case 2: Aggregation; capitalize the first characters
+  return `${
+    aggregationInterval.slice(0, 1).toUpperCase() + aggregationInterval.slice(1)
+  } ${aggregationType.slice(0, 1).toUpperCase() + aggregationType.slice(1)}`;
+};
+
+/**
+ * Create a full string for a line chart or column chart title
+ * @param {Array} datastreamNamesArr An array of datastream names as strings
+ * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
+ * @param {String} aggregationType The aggregation type as a string, either "sum" or "average"
+ * @returns {String} Full string for chart title
+ */
+const createFullTitleForLineOrColumnChart = function (
+  phenomenonNamesArr,
+  aggregationInterval,
+  aggregationType
+) {
+  // Case 1: No aggregation; create a comma separated string of phenomenon names
+  if (!aggregationInterval && !aggregationType)
+    return `${createPartialTitleForLineOrColumnChart(
+      aggregationInterval,
+      aggregationType
+    )}${createCombinedTextDelimitedByComma(phenomenonNamesArr)}`;
+
+  // Case 2: Aggregation
+  return `${createPartialTitleForLineOrColumnChart(
+    aggregationInterval,
+    aggregationType
+  )} ${phenomenonNamesArr[0]}`;
+};
+
+/**
+ * Creates a date string that is used in a shared tooltip for a line or column chart
+ * @param {Number} pointXAxisValue The x-axis value (Unix timestamp) which is common for a set of data points
+ * @param {String} aggregationInterval The aggregation interval as a string, either "daily" or "monthly"
+ * @returns {String} A calendar date or calendar month string that is common for a set of data points
+ */
+const createTooltipDateString = function (
+  pointXAxisValue,
+  aggregationInterval
+) {
+  if (aggregationInterval === undefined || aggregationInterval === "daily")
+    // When `aggregationInterval === undefined`, assume that we are displaying raw observations
+    return `${Highcharts.dateFormat("%A, %b %e, %Y", pointXAxisValue)}`;
+  else if (aggregationInterval === "monthly")
+    return `${Highcharts.dateFormat("%b %Y", pointXAxisValue)}`;
+};
+
 /**
  * Extracts the sampling rate substring from a datastream name string
  * @param {Array} datastreamNamesArr An array of datastream name(s)
@@ -69,10 +131,22 @@ const extractSamplingRateFromDatastreamName = function (datastreamNamesArr) {
   );
 };
 
+/**
+ * Remove the transparency (alpha channel) from a color
+ * @param {String} rgbaColor A color expressed in RGBA format
+ * @returns {String} A color in RGB format
+ */
+const removeTransparencyFromColor = function (rgbaColor) {
+  return `rgb(${rgbaColor.slice(5, -5)})`;
+};
+
 export {
   chartExportOptions,
   createCombinedTextDelimitedByAmpersand,
   createCombinedTextDelimitedByComma,
+  createFullTitleForLineOrColumnChart,
+  createTooltipDateString,
   convertHexColorToRGBColor,
   extractSamplingRateFromDatastreamName,
+  removeTransparencyFromColor,
 };
diff --git a/public/js/src_modules/chartLine.mjs b/public/js/src_modules/chartLine.mjs
index a8f6698..26af913 100644
--- a/public/js/src_modules/chartLine.mjs
+++ b/public/js/src_modules/chartLine.mjs
@@ -2,15 +2,17 @@
 
 import {
   chartExportOptions,
+  createFullTitleForLineOrColumnChart,
+  createTooltipDateString,
   extractSamplingRateFromDatastreamName,
 } from "./chartHelpers.mjs";
 
 /**
- * Format the response from SensorThings API to make it suitable for use in a line chart
+ * Format the response from SensorThings API to make it suitable for use in a line chart or column chart
  * @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 line chart
  */
-const formatSensorThingsApiResponseForLineChart = function (obsArray) {
+const formatSensorThingsApiResponseForLineOrColumnChart = function (obsArray) {
   if (!obsArray) return;
 
   return obsArray.map((result) => {
@@ -32,12 +34,12 @@ const createCombinedTextForLineChartTitles = function (phenomenonNamesArr) {
 /**
  * Creates an options object for each series drawn in the line chart
  * @param {Array} formattedObsArraysForLineChart An array of formatted observation array(s) from one or more datastreams
- * @param {Array} phenomenonNamesArr An array of phenomenon name(s)
+ * @param {Array} buildingIdsPhenomenonNamesArr An array of string(s) made up of building ID(s) + phenomenon name(s)
  * @returns {Array} An array made up of series options object(s)
  */
 const createSeriesOptionsForLineChart = function (
   formattedObsArraysForLineChart,
-  phenomenonNamesArr
+  buildingIdsPhenomenonNamesArr
 ) {
   // An array of colors, in hexadecimal format, provided by the global Highcharts object
   const seriesColors = Highcharts.getOptions().colors;
@@ -46,16 +48,19 @@ const createSeriesOptionsForLineChart = function (
   const seriesColorsArr = [...seriesColors];
 
   // Create an array of seriesOptions objects
-  // Assumes that the observation array of arrays and phenomenon names array are of equal length
+  // Assumes that the observation array of arrays and building IDs + phenomenon names array are of equal length
   // Use one of the arrays for looping
-  if (formattedObsArraysForLineChart.length !== phenomenonNamesArr.length)
+  if (
+    formattedObsArraysForLineChart.length !==
+    buildingIdsPhenomenonNamesArr.length
+  )
     throw new Error(
       "The observations array and phenomenon names array have different lengths"
     );
 
   return formattedObsArraysForLineChart.map((formattedObsArray, i) => {
     return {
-      name: `${phenomenonNamesArr[i]}`,
+      name: `${buildingIdsPhenomenonNamesArr[i]}`,
       data: formattedObsArray,
       color: seriesColorsArr[i],
       turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
@@ -74,15 +79,40 @@ const drawLineChartHighcharts = function (
   extractedFormattedDatastreamProperties
 ) {
   // Arrays of datastream properties
-  const {
-    datastreamNamesArr,
+  let datastreamNamesArr,
     phenomenonNamesArr,
+    buildingIdsPhenomenonNamesArr,
     unitOfMeasurementSymbolsArr,
-  } = extractedFormattedDatastreamProperties;
+    aggregationInterval,
+    aggregationType;
+
+  // Check whether the datastream properties are for aggregated observations
+  if (extractedFormattedDatastreamProperties?.aggregationType === undefined) {
+    // Case 1: No aggregation
+    ({
+      datastreamNamesArr,
+      phenomenonNamesArr,
+      buildingIdsPhenomenonNamesArr,
+      unitOfMeasurementSymbolsArr,
+    } = extractedFormattedDatastreamProperties);
+  } else {
+    // Case 2: Aggregation
+    ({
+      datastreamNamesArr,
+      phenomenonNamesArr,
+      buildingIdsPhenomenonNamesArr,
+      unitOfMeasurementSymbolsArr,
+      aggregationInterval,
+      aggregationType,
+    } = extractedFormattedDatastreamProperties);
+  }
 
   // Chart title and subtitle text
-  const textChartTitle =
-    createCombinedTextForLineChartTitles(phenomenonNamesArr);
+  const textChartTitle = createFullTitleForLineOrColumnChart(
+    phenomenonNamesArr,
+    aggregationInterval,
+    aggregationType
+  );
 
   const textChartSubtitle = `Sampling rate(s): ${createCombinedTextForLineChartTitles(
     extractSamplingRateFromDatastreamName(datastreamNamesArr)
@@ -91,7 +121,7 @@ const drawLineChartHighcharts = function (
   // Create the array of series options object(s)
   const seriesOptionsArr = createSeriesOptionsForLineChart(
     formattedObsArraysForLineChart,
-    phenomenonNamesArr,
+    buildingIdsPhenomenonNamesArr,
     unitOfMeasurementSymbolsArr
   );
 
@@ -119,7 +149,9 @@ const drawLineChartHighcharts = function (
         // Our tooltip is split
         // this.x -- common for all points
         // this.points -- an array containing properties for each series
-        return [`${Highcharts.dateFormat("%A, %b %e, %Y", this.x)}`].concat(
+        return [
+          `${createTooltipDateString(this.x, aggregationInterval)}`,
+        ].concat(
           this.points
             ? this.points.map(
                 (point, i) =>
@@ -140,4 +172,7 @@ const drawLineChartHighcharts = function (
   });
 };
 
-export { formatSensorThingsApiResponseForLineChart, drawLineChartHighcharts };
+export {
+  formatSensorThingsApiResponseForLineOrColumnChart,
+  drawLineChartHighcharts,
+};
diff --git a/public/js/src_modules/chartScatterPlot.mjs b/public/js/src_modules/chartScatterPlot.mjs
index 3b9b878..d690741 100644
--- a/public/js/src_modules/chartScatterPlot.mjs
+++ b/public/js/src_modules/chartScatterPlot.mjs
@@ -6,6 +6,7 @@ import {
   createCombinedTextDelimitedByAmpersand,
   createCombinedTextDelimitedByComma,
   extractSamplingRateFromDatastreamName,
+  removeTransparencyFromColor,
 } from "./chartHelpers.mjs";
 
 /**
@@ -457,12 +458,18 @@ const drawScatterPlotHighcharts = function (
 
     tooltip: {
       formatter() {
-        const headerString = `${this.series.name}<br>`;
+        // The color contained in the series object is in RGBA format,
+        // convert it to RGB format so that the text in the tooltip is more legible
+        const headerString = `<span style="color:${removeTransparencyFromColor(
+          this.point.color
+        )}">${this.series.name}</span> <br>`;
+
         const pointString = `<b>${this.point.y.toFixed(
           2
         )} ${UNIT_OF_MEASUREMENT_SYMBOL}, ${this.point.x.toFixed(
           2
         )} ${UNIT_OF_MEASUREMENT_SYMBOL}</b>`;
+
         return headerString + pointString;
       },
     },
diff --git a/public/js/src_modules/fetchedDataProcess.mjs b/public/js/src_modules/fetchedDataProcessing.mjs
similarity index 87%
rename from public/js/src_modules/fetchedDataProcess.mjs
rename to public/js/src_modules/fetchedDataProcessing.mjs
index e766b5e..7d40960 100644
--- a/public/js/src_modules/fetchedDataProcess.mjs
+++ b/public/js/src_modules/fetchedDataProcessing.mjs
@@ -46,21 +46,9 @@ const extractBuildingIdPhenomenonNameFromDatastreamName = function (
 /**
  * Format the response containing a Datastream's metadata from Sensorthings API
  * @param {Object} datastreamMetadata An object containing a Datastream's metadata
- * @param {Boolean} isMetadataForAggregation A flag to determine if the datastream metadata will be used for aggregation. Set to `true` if metadata will be used for aggregation, `false` if not
  * @returns {Object} An object containing the formatted metadata that is suitable for use in a chart
  */
-const formatDatastreamMetadataForChart = function (
-  datastreamMetadata,
-  isMetadataForAggregation
-) {
-  if (
-    datastreamMetadata === undefined ||
-    isMetadataForAggregation === undefined
-  )
-    throw new Error(
-      "This function expects two arguments, ensure that both have been supplied"
-    );
-
+const formatDatastreamMetadataForChart = function (datastreamMetadata) {
   const {
     description: datastreamDescription,
     name: datastreamName,
@@ -80,19 +68,10 @@ const formatDatastreamMetadataForChart = function (
     unitOfMeasurement.symbol
   );
 
-  // Case 1: Metadata NOT USED for aggregation; "isMetadataForAggregation" = false
-  if (!isMetadataForAggregation)
-    return {
-      datastreamDescription,
-      datastreamName,
-      phenomenonName,
-      unitOfMeasurementSymbol,
-    };
-
-  // Case 2: Metadata USED for aggregation; "isMetadataForAggregation" = true
   return {
     datastreamDescription,
     datastreamName,
+    phenomenonName,
     buildingIdPhenomenonName,
     unitOfMeasurementSymbol,
   };
@@ -174,6 +153,7 @@ const extractPropertiesFromFormattedDatastreamMetadata = function (
       datastreamDescriptionsArr,
       datastreamNamesArr,
       phenomenonNamesArr,
+      buildingIdsPhenomenonNamesArr,
       unitOfMeasurementSymbolsArr,
     };
 
@@ -181,6 +161,7 @@ const extractPropertiesFromFormattedDatastreamMetadata = function (
   return {
     datastreamDescriptionsArr,
     datastreamNamesArr,
+    phenomenonNamesArr,
     buildingIdsPhenomenonNamesArr,
     unitOfMeasurementSymbolsArr,
     aggregationInterval,
-- 
GitLab