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