diff --git a/public/js/appChart.js b/public/js/appChart.js index 338efe4c53a2cb61a47f6a1c279ccac659c9e914..7e2cc390c758358da14372ded98be3f06713c102 100644 --- a/public/js/appChart.js +++ b/public/js/appChart.js @@ -25,11 +25,12 @@ import { drawColumnChartHighcharts, } from "./src_modules/chartColumn.mjs"; +import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs"; + import { formatDatastreamMetadataForChart, extractPropertiesFromFormattedDatastreamMetadata, - getMetadataPlusObservationsFromSingleOrMultipleDatastreams, -} from "./src_modules/fetchData.mjs"; +} from "./src_modules/fetchedDataProcess.mjs"; import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs"; @@ -73,13 +74,14 @@ const drawHeatmapHCUsingTempDifference = async function () { // Format the metadata const formattedTempDiff225MetadataNestedArr = metadataTemperatureDiff225NestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) + formatDatastreamMetadataForChart(metadataObj, false) ); // Extract the formatted metadata properties const extractedFormattedTempDiff225Properties = extractPropertiesFromFormattedDatastreamMetadata( - formattedTempDiff225MetadataNestedArr + formattedTempDiff225MetadataNestedArr, + false ); // First need to extract the formatted observations from the nested array @@ -127,13 +129,14 @@ const drawScatterPlotHCTest2 = async function () { // Create formatted array(s) for metadata const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) + formatDatastreamMetadataForChart(metadataObj, false) ); // Extract the formatted metadata properties const extractedFormattedDatastreamProperties = extractPropertiesFromFormattedDatastreamMetadata( - formattedMetadataNestedArr + formattedMetadataNestedArr, + false ); drawScatterPlotHighcharts( @@ -175,13 +178,14 @@ const testLineChartMultipleSeries = async function () { // Format the metadata const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) => - formatDatastreamMetadataForChart(metadataArr) + formatDatastreamMetadataForChart(metadataArr, false) ); // Extract the formatted metadata properties const extractedFormattedDatastreamProperties = extractPropertiesFromFormattedDatastreamMetadata( - formattedMetadataNestedArr + formattedMetadataNestedArr, + false ); drawLineChartHighcharts( @@ -260,13 +264,16 @@ const drawColumnChartMonthlySumTest = async function () { // Format the metadata const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) + formatDatastreamMetadataForChart(metadataObj, true) ); // Extract the formatted metadata properties const extractedFormattedDatastreamProperties = extractPropertiesFromFormattedDatastreamMetadata( - formattedMetadataNestedArr + formattedMetadataNestedArr, + true, + "monthly", + "sum" ); drawColumnChartHighcharts( @@ -339,13 +346,16 @@ const drawColumnChartDailySumTest = async function () { // Format the metadata const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) + formatDatastreamMetadataForChart(metadataObj, true) ); // Extract the formatted metadata properties const extractedFormattedDatastreamProperties = extractPropertiesFromFormattedDatastreamMetadata( - formattedMetadataNestedArr + formattedMetadataNestedArr, + true, + "daily", + "sum" ); drawColumnChartHighcharts( @@ -423,14 +433,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) + formatDatastreamMetadataForChart(metadataObj, false) ); // 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 + formattedMetadataNestedArr, + false ); drawLineChartHighcharts( @@ -502,14 +515,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) + formatDatastreamMetadataForChart(metadataObj, false) ); // 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 + formattedMetadataNestedArr, + false ); drawLineChartHighcharts( diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js index e498776f2dede2f9f30e4fda1b3ad0bdf2b1cd93..c765c8b5f758a3f4af1e4cc7811732ca5e2a3e16 100644 --- a/public/js/dropDownList.js +++ b/public/js/dropDownList.js @@ -5,11 +5,12 @@ import { QUERY_PARAMS_COMBINED, } from "./src_modules/baseUrlPlusQueryParams.mjs"; +import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs"; + import { formatDatastreamMetadataForChart, extractPropertiesFromFormattedDatastreamMetadata, - getMetadataPlusObservationsFromSingleOrMultipleDatastreams, -} from "./src_modules/fetchData.mjs"; +} from "./src_modules/fetchedDataProcess.mjs"; import { formatSensorThingsApiResponseForLineChart, @@ -84,7 +85,7 @@ const getUniqueValues = function (dataArr, index) { /** * Populate the HTML elements that make up a drop down list - * @param {String} element String corresponding to the ID of a drop down HTML element + * @param {Object} element HTMLElement object of a drop down list element * @param {Array} uniqueValuesArr An array of unique values * @returns {undefined} */ @@ -100,8 +101,8 @@ const populateDropDown = function (element, uniqueValuesArr) { /** * Filter an array using filter strings of variable length - * @param {*} dataArr Input array - * @param {*} filtersAsArray An array of filter strings + * @param {Array} dataArr Input array + * @param {Array} filtersAsArray An array of filter strings * @returns {Array} An array that contains the filter result */ const filterArray = function (dataArr, filtersAsArray) { @@ -112,9 +113,9 @@ const filterArray = function (dataArr, filtersAsArray) { /** * Create a drop down list in the HTML document - * @param {*} dataArr Input array - * @param {*} filtersAsArray An array of strings tp be used as filters - * @param {*} targetElement String corresponding to the ID of a drop down HTML element + * @param {Array} dataArr Input array + * @param {Array} filtersAsArray An array of strings tp be used as filters + * @param {Object} targetElement HTMLElement object of a drop down list element * @returns {undefined} */ const makeDropDown = function (dataArr, filtersAsArray, targetElement) { @@ -292,12 +293,15 @@ const selectChartTypeFromDropDown = async function () { // Create formatted array(s) for metadata - same for both chart types const formattedMetadataArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) + formatDatastreamMetadataForChart(metadataObj, false) ); // Extract the formatted metadata properties const extractedFormattedDatastreamProperties = - extractPropertiesFromFormattedDatastreamMetadata(formattedMetadataArr); + extractPropertiesFromFormattedDatastreamMetadata( + formattedMetadataArr, + false + ); if (selectedChartType === "Line") { drawLineChartHighcharts( diff --git a/public/js/src_modules/aggregate.mjs b/public/js/src_modules/aggregate.mjs index 1174ffc0543100212d46fe6eec644ca40efae4a2..6691c1c3f1838999a8ae84d78277a94ae46df64a 100644 --- a/public/js/src_modules/aggregate.mjs +++ b/public/js/src_modules/aggregate.mjs @@ -14,7 +14,7 @@ const calculateSumOfObservationValuesWithinDatesInterval = function ( obsValuesForDaysIntervalArr ) { return obsValuesForDaysIntervalArr.reduce( - (accumulator, currentValue) => accumulator + currentValue + (accumulator, obsValue) => accumulator + obsValue ); }; @@ -42,7 +42,7 @@ const calculateSumOfObservationValuesWithinMonthInterval = function ( obsValuesForMonthIntervalArr ) { return obsValuesForMonthIntervalArr.reduce( - (accumulator, currentValue) => accumulator + currentValue + (accumulator, obsValue) => accumulator + obsValue ); }; diff --git a/public/js/src_modules/calculateTemperatureDiff.mjs b/public/js/src_modules/calculateTemperatureDiff.mjs index afc2aac60547935f5e5471384c0077578a5579fa..157ae45ffb1d73285aa46c06a033578fe948e46d 100644 --- a/public/js/src_modules/calculateTemperatureDiff.mjs +++ b/public/js/src_modules/calculateTemperatureDiff.mjs @@ -1,9 +1,8 @@ "use strict"; -import { - extractPhenomenonNameFromDatastreamName, - getMetadataPlusObservationsFromSingleOrMultipleDatastreams, -} from "./fetchData.mjs"; +import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./fetchData.mjs"; + +import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcess.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 1da6590c29060ef74d51c77b60e1e840903958d3..cb0b39e406a53996b37d177b4cd56f997b98abe1 100644 --- a/public/js/src_modules/chartColumn.mjs +++ b/public/js/src_modules/chartColumn.mjs @@ -1,6 +1,12 @@ "use strict"; -import { chartExportOptions } from "./chartHelpers.mjs"; +import { + chartExportOptions, + createCombinedTextDelimitedByComma, + extractSamplingRateFromDatastreamName, +} from "./chartHelpers.mjs"; + +import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcess.mjs"; /** * Format a computed aggregation result to make it suitable for a column chart @@ -30,20 +36,19 @@ const formatAggregationResultForColumnChart = function ( /** * Creates an options object for each series drawn in a column chart * @param {Array} formattedAggregatedResultForColumnChart An array of formatted aggregated result array(s) from one or more datastreams - * @param {Array} phenomenonNamesArr An array of phenomenon name(s) - * @param {Array} phenomenonSymbolsArr An array of phenomenon symbol(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 createSeriesOptionsForColumnChart = function ( formattedAggregatedResultForColumnChart, - phenomenonNamesArr, - phenomenonSymbolsArr + buildingIdsPhenomenonNamesArr ) { // 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 + formattedAggregatedResultForColumnChart.length !== + buildingIdsPhenomenonNamesArr.length ) throw new Error( "The observations array and phenomenon names array have different lengths" @@ -52,7 +57,7 @@ const createSeriesOptionsForColumnChart = function ( return formattedAggregatedResultForColumnChart.map( (formattedAggResArray, i) => { return { - name: `${phenomenonNamesArr[i]} (${phenomenonSymbolsArr[i]})`, + name: `${buildingIdsPhenomenonNamesArr[i]}`, data: formattedAggResArray, turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release }; @@ -60,6 +65,64 @@ 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 @@ -73,16 +136,39 @@ const drawColumnChartHighcharts = function ( // Arrays of datastream properties const { datastreamNamesArr, - phenomenonNamesArr, + buildingIdsPhenomenonNamesArr, unitOfMeasurementSymbolsArr, + aggregationInterval, + aggregationType, } = extractedFormattedDatastreamProperties; + // Create an array of phenomenon names + const phenomenonNamesArr = datastreamNamesArr.map((datastreamName) => + extractPhenomenonNameFromDatastreamName(datastreamName) + ); + + // Create the array of series options object(s) const seriesOptionsArr = createSeriesOptionsForColumnChart( formattedAggResultArraysForColumnChart, + buildingIdsPhenomenonNamesArr + ); + + // Assume that we will be comparing similar phenomena, so we can use the first phenomenon name + const phenomenonName = phenomenonNamesArr[0]; + + // Assume that we will be comparing similar phenomena, so we can use the first phenomenon symbol + const unitOfMeasurementSymbol = unitOfMeasurementSymbolsArr[0]; + + const textChartTitle = createFullTitleForColumnChart( phenomenonNamesArr, - unitOfMeasurementSymbolsArr + aggregationInterval, + aggregationType ); + const textChartSubtitle = `Sampling rate(s): ${createCombinedTextDelimitedByComma( + extractSamplingRateFromDatastreamName(datastreamNamesArr) + )}`; + Highcharts.chart("chart-column", { chart: { type: "column", @@ -90,11 +176,11 @@ const drawColumnChartHighcharts = function ( }, title: { - text: "Monthly Average Rainfall", + text: textChartTitle, }, subtitle: { - text: "Source: WorldClimate.com", + text: textChartSubtitle, }, xAxis: { @@ -110,21 +196,23 @@ const drawColumnChartHighcharts = function ( }, tooltip: { - headerFormat: ` - <span style="font-size:10px">{point.key}</span> - <table> - `, - pointFormat: ` - <tr> - <td style="color:{series.color};padding:0">{series.name}: </td> - <td style="padding:0"><b>{point.y:.1f} mm</b></td> - </tr> - `, - footerFormat: ` - </table> - `, + formatter() { + // Our tooltip is shared + // this.x -- common for all points + // this.points -- an array containing properties for each series + // Note that our `reduce` method is in this format: + // ((accumulator, currentValue, currentIndex) => {...}, initialValue) + return this.points.reduce( + (accumulator, point, i) => + `${accumulator} <br/> <span style="color:${point.color}">${ + point.series.name + }</span>: <b>${point.y.toFixed(2)} ${ + unitOfMeasurementSymbolsArr[i] + }</b>`, + `${createHeaderDateString(this.x, aggregationInterval)}` + ); + }, shared: true, - useHTML: true, }, plotOptions: { diff --git a/public/js/src_modules/fetchData.mjs b/public/js/src_modules/fetchData.mjs index 0311398ccdcb8ab4e088692357667ff38e95f137..9064ed667435dffc55dcc2d7faa5bf408a385f63 100644 --- a/public/js/src_modules/fetchData.mjs +++ b/public/js/src_modules/fetchData.mjs @@ -81,99 +81,6 @@ 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 - * @returns {Object} An object containing the formatted metadata that is suitable for use in a line chart or heatmap - */ -const formatDatastreamMetadataForChart = function (datastreamMetadata) { - const { - description: datastreamDescription, - name: datastreamName, - unitOfMeasurement, - } = datastreamMetadata; - - // Extract phenomenon name from Datastream name - const phenomenonName = - extractPhenomenonNameFromDatastreamName(datastreamName); - - // Get the unitOfMeasurement's symbol - const unitOfMeasurementSymbol = matchUnitOfMeasurementSymbolStringToSymbol( - unitOfMeasurement.symbol - ); - - return { - datastreamDescription, - datastreamName, - phenomenonName, - unitOfMeasurementSymbol, - }; -}; - -/** - * Extract the properties that make up the formatted datastream metadata object(s) - * @param {Array} formattedDatastreamsMetadataArr An array of formatted metadata object(s) from one or more datastreams - * @returns {Object} An object that contains array(s) of formatted datastream metadata properties - */ -const extractPropertiesFromFormattedDatastreamMetadata = function ( - formattedDatastreamsMetadataArr -) { - // Create arrays from the properties of the formatted datastream metadata - const datastreamDescriptionsArr = formattedDatastreamsMetadataArr.map( - (datastreamMetadata) => datastreamMetadata.datastreamDescription - ); - - const datastreamNamesArr = formattedDatastreamsMetadataArr.map( - (datastreamMetadata) => datastreamMetadata.datastreamName - ); - - const phenomenonNamesArr = formattedDatastreamsMetadataArr.map( - (datastreamMetadata) => datastreamMetadata.phenomenonName - ); - - const unitOfMeasurementSymbolsArr = formattedDatastreamsMetadataArr.map( - (datastreamMetadata) => datastreamMetadata.unitOfMeasurementSymbol - ); - - return { - datastreamDescriptionsArr, - datastreamNamesArr, - phenomenonNamesArr, - unitOfMeasurementSymbolsArr, - }; -}; - /** * Traverses all the pages that make up the response from a SensorThingsAPI instance. The link to the next page, if present, is denoted by the presence of a "@iot.nextLink" property in the response object. This function concatenates all the values so that the complete results are returned in one array. * @async @@ -321,9 +228,4 @@ const getMetadataPlusObservationsFromSingleOrMultipleDatastreams = } }; -export { - extractPhenomenonNameFromDatastreamName, - formatDatastreamMetadataForChart, - extractPropertiesFromFormattedDatastreamMetadata, - getMetadataPlusObservationsFromSingleOrMultipleDatastreams, -}; +export { getMetadataPlusObservationsFromSingleOrMultipleDatastreams }; diff --git a/public/js/src_modules/fetchedDataProcess.mjs b/public/js/src_modules/fetchedDataProcess.mjs new file mode 100644 index 0000000000000000000000000000000000000000..e766b5ebe5cc312be93c9e71b4612384d4092e56 --- /dev/null +++ b/public/js/src_modules/fetchedDataProcess.mjs @@ -0,0 +1,195 @@ +"use strict"; + +/** + * 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 +}; + +/** + * Extract the building Id and phenomenon name from a Datastream's name + * @param {String} datastreamName A string representing the Datastream's name + * @returns {String} The extracted building ID and phenomenon name + */ +const extractBuildingIdPhenomenonNameFromDatastreamName = function ( + datastreamName +) { + // The negative index should remove these nine characters: ` DS:60min` + return datastreamName.slice(0, -9); +}; + +/** + * 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 { + description: datastreamDescription, + name: datastreamName, + unitOfMeasurement, + } = datastreamMetadata; + + // Extract phenomenon name from Datastream name + const phenomenonName = + extractPhenomenonNameFromDatastreamName(datastreamName); + + // Extract building ID + phenomenon name from Datastream name + const buildingIdPhenomenonName = + extractBuildingIdPhenomenonNameFromDatastreamName(datastreamName); + + // Get the unitOfMeasurement's symbol + const unitOfMeasurementSymbol = matchUnitOfMeasurementSymbolStringToSymbol( + 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, + buildingIdPhenomenonName, + unitOfMeasurementSymbol, + }; +}; + +/** + * Extract the properties that make up the formatted datastream metadata object(s) + * @param {Array} formattedDatastreamsMetadataArr An array of formatted metadata object(s) from one or more datastreams + * @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 + * @param {String} [aggregationInterval] The aggregation interval as a string, either "daily" or "monthly". Required when `isMetadataForAggregation = true` + * @param {String} [aggregationType] The aggregation type as a string, either "sum" or "average". Required when `isMetadataForAggregation = true` + * @returns {Object} An object that contains array(s) of formatted datastream metadata properties + */ +const extractPropertiesFromFormattedDatastreamMetadata = function ( + formattedDatastreamsMetadataArr, + isMetadataForAggregation, + aggregationInterval, + aggregationType +) { + if ( + formattedDatastreamsMetadataArr === undefined || + isMetadataForAggregation === undefined + ) + throw new Error( + "This function expects two arguments, ensure that both have been supplied" + ); + + if ( + formattedDatastreamsMetadataArr && + isMetadataForAggregation && + (aggregationInterval === undefined || aggregationType === undefined) + ) + throw new Error( + "This function expects four arguments, ensure that all of them have been supplied" + ); + + if ( + isMetadataForAggregation && + aggregationInterval !== "daily" && + aggregationInterval !== "monthly" + ) + throw new Error( + `The supported aggegation interval strings are "daily" or "monthly"` + ); + + if ( + isMetadataForAggregation && + aggregationType !== "sum" && + aggregationType !== "average" + ) + throw new Error( + `The supported aggegation type strings are "sum" or "average"` + ); + + // Create arrays from the properties of the formatted datastream metadata + const datastreamDescriptionsArr = formattedDatastreamsMetadataArr.map( + (datastreamMetadata) => datastreamMetadata.datastreamDescription + ); + + const datastreamNamesArr = formattedDatastreamsMetadataArr.map( + (datastreamMetadata) => datastreamMetadata.datastreamName + ); + + const phenomenonNamesArr = formattedDatastreamsMetadataArr.map( + (datastreamMetadata) => datastreamMetadata.phenomenonName + ); + + const buildingIdsPhenomenonNamesArr = formattedDatastreamsMetadataArr.map( + (datastreamMetadata) => datastreamMetadata.buildingIdPhenomenonName + ); + + const unitOfMeasurementSymbolsArr = formattedDatastreamsMetadataArr.map( + (datastreamMetadata) => datastreamMetadata.unitOfMeasurementSymbol + ); + + // Case 1: Metadata NOT USED for aggregation; "isMetadataForAggregation" = false + if (!isMetadataForAggregation) + return { + datastreamDescriptionsArr, + datastreamNamesArr, + phenomenonNamesArr, + unitOfMeasurementSymbolsArr, + }; + + // Case 2: Metadata USED for aggregation; "isMetadataForAggregation" = true + return { + datastreamDescriptionsArr, + datastreamNamesArr, + buildingIdsPhenomenonNamesArr, + unitOfMeasurementSymbolsArr, + aggregationInterval, + aggregationType, + }; +}; + +export { + extractPhenomenonNameFromDatastreamName, + formatDatastreamMetadataForChart, + extractPropertiesFromFormattedDatastreamMetadata, +};