diff --git a/index.html b/index.html index d51f644af8dcc2559ec2c264a0ab8342ed59b7ed..a355149988562ee516c348ae275fe3cc08229d5a 100644 --- a/index.html +++ b/index.html @@ -69,7 +69,6 @@ Custom JS --> <script defer type="module" src="js/appCesium.js"></script> <script defer type="module" src="js/appChart.js"></script> - <script defer type="module" src="js/dropDownList.js"></script> </head> <body class="sb-nav-fixed"> <nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark"> @@ -131,6 +130,21 @@ <label for="droneMode">Drone View</label> </div> <br /> --> + <div id="drop-down--chart-type-parent"> + <span><strong>Chart type</strong></span> + <div class="nowrap"> + <!-- We need the `multiple` attribute for the dropdowns even if + we do not need to support multiple selections. This seems to + be a quirk of the `vanillaSelectBox` library --> + <select id="drop-down--chart-type" multiple> + <option>Column</option> + <option>Line</option> + <option>Heatmap</option> + <option>Scatter Plot</option> + </select> + </div> + </div> + <br /> <div id="drop-down--bldg-data-point-parent"> <span><strong>Building(s), Data Point(s)</strong></span> <div class="nowrap"> @@ -140,6 +154,9 @@ size="5" > <!-- Note: The values of the option elements have to be unique --> + <optgroup label="Other"> + <option>Außentemp</option> + </optgroup> <optgroup label="Bau 101"> <option>101/VL</option> <option>101/RL</option> @@ -174,9 +191,6 @@ <option>225/Energie</option> <option>225/Energie_VERBR</option> </optgroup> - <optgroup label="Other"> - <option>Außentemp</option> - </optgroup> </select> </div> </div> @@ -216,21 +230,6 @@ </div> </div> <br /> - <div id="drop-down--chart-type-parent"> - <span><strong>Chart type</strong></span> - <div class="nowrap"> - <!-- We need the `multiple` attribute for the dropdowns even if - we do not need to support multiple selections. This seems to - be a quirk of the `vanillaSelectBox` library --> - <select id="drop-down--chart-type" multiple> - <option>Column</option> - <option>Line</option> - <option>Heatmap</option> - <option>Scatter Plot</option> - </select> - </div> - </div> - <br /> <button id="btn-draw-chart">Draw Chart</button> <!-- <span><strong>Display Options</strong></span> <div class="nowrap"> diff --git a/public/js/appChart.js b/public/js/appChart.js index 4a49952384c662bd202b7b5da1951d7b21867f3b..638884c6019c7cbe3cd4b4f22cc517bf1cd83a96 100644 --- a/public/js/appChart.js +++ b/public/js/appChart.js @@ -5,765 +5,380 @@ import { QUERY_PARAMS_COMBINED, } from "./src_modules/baseUrlPlusQueryParams.mjs"; -import { - formatSensorThingsApiResponseForLineOrColumnChart, - drawLineChartHighcharts, -} from "./src_modules/chartLine.mjs"; +import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs"; -import { - formatSensorThingsApiResponseForHeatMap, - drawHeatMapHighcharts, -} from "./src_modules/chartHeatmap.mjs"; +import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs"; import { - formatSensorThingsApiResponseForScatterPlot, - drawScatterPlotHighcharts, -} from "./src_modules/chartScatterPlot.mjs"; + formatDatastreamMetadataForChart, + extractPropertiesFromFormattedDatastreamMetadata, +} from "./src_modules/fetchedDataProcessing.mjs"; import { - formatAggregationResultForColumnChart, - drawColumnChartHighcharts, -} from "./src_modules/chartColumn.mjs"; + formatSensorThingsApiResponseForLineOrColumnChart, + drawLineChartHighcharts, +} from "./src_modules/chartLine.mjs"; -import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs"; +import { drawColumnChartHighcharts } from "./src_modules/chartColumn.mjs"; import { - formatDatastreamMetadataForChart, - extractPropertiesFromFormattedDatastreamMetadata, -} from "./src_modules/fetchedDataProcessing.mjs"; + showLoadingSpinner, + hideLoadingSpinner, +} from "./src_modules/loadingIndicator.mjs"; -import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs"; +import { vanillaSelectBox } from "./thirdparty/vanillaSelectBox.mjs"; import { extractObservationsWithinDatesInterval, extractUniqueCalendarDatesFromTimestamp, - extractUniqueCalendarMonthsFromCalendarDates, } from "./src_modules/aggregateHelpers.mjs"; import { - calculateMinimumObservationValuesWithinInterval, - calculateMaximumObservationValuesWithinInterval, - calculateSumOfObservationValuesWithinInterval, - calculateAverageOfObservationValuesWithinInterval, -} from "./src_modules/aggregate.mjs"; - -/** - * Test plotting of temp difference (dT) using heatmap - */ -const drawHeatmapHCUsingTempDifference = async function () { - try { - const [observationsTemperatureDiff225Arr, metadataTemperatureDiff225Arr] = - await calculateVorlaufMinusRuecklaufTemperature( - BASE_URL, - QUERY_PARAMS_COMBINED, - "225", - "60min" - ); - - // We want to have nested arrays, so as to mimick the nested responses we get from fetching observations + metadata - const observationsTemperatureDiff225NestedArr = [ - observationsTemperatureDiff225Arr, - ]; + splitMultipleOptionsTextDelimitedBySlash, + getSelectedOptionsFromAllDropDownLists, + checkIfSelectedOptionsContainTemperatureDifference, + deleteTemperatureDifferenceOptions, + extractTemperatureDifferenceOptions, + extractBuildingPlusSamplingRate, + checkIfChartRequiresRawObservations, + getAbbreviationsForSelectedOptionsFromAllDropDownLists, +} from "./src_modules/dropDownListHelpers.mjs"; - const metadataTemperatureDiff225NestedArr = [metadataTemperatureDiff225Arr]; - - // Format the observations - const formattedTempDiff225NestedArr = - observationsTemperatureDiff225NestedArr.map((obsArr) => - formatSensorThingsApiResponseForHeatMap(obsArr) - ); - - // Format the metadata - const formattedTempDiff225MetadataNestedArr = - metadataTemperatureDiff225NestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) - ); - - // Extract the formatted metadata properties - const extractedFormattedTempDiff225Properties = - extractPropertiesFromFormattedDatastreamMetadata( - formattedTempDiff225MetadataNestedArr, - false - ); - - // First need to extract the formatted observations from the nested array - // Heatmap only needs one set of formatted observation values - drawHeatMapHighcharts( - ...formattedTempDiff225NestedArr, - extractedFormattedTempDiff225Properties - ); - } catch (err) { - console.error(err); - } -}; +import { + drawHeatmapBasedOnSelectedOptions, + drawScatterPlotFromChartSelection, + drawLineChartBasedOnSelectedAggregationOptions, + drawColumnChartBasedOnSelectedAggregationOptions, +} from "./src_modules/dropDownListProcessing.mjs"; /** - * Test drawing of scatter plot chart + * Use the `vanillaDropDown` library to style the buildings & data points drop down list + * + * @returns {undefined} */ -const drawScatterPlotHCTest2 = async function () { - try { - const sensorsOfInterestNestedArr = [ - ["weather_station_521", "outside_temp", "60min"], - ["225", "vl", "60min"], - ["125", "rl", "60min"], - ]; - - const observationsPlusMetadata = - await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( - BASE_URL, - QUERY_PARAMS_COMBINED, - sensorsOfInterestNestedArr - ); - - // Extract the combined arrays for observations and metadata - const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata; - - // Extract values for x-axis and y-axis - // x-axis values are first element of nested observations array - const [obsXAxisArr] = observationsNestedArr.slice(0, 1); - // y-axis values are rest of elements of nested observations array - const obsYAxisNestedArr = observationsNestedArr.slice(1); - - // Create formatted array(s) for observations - const formattedObservationsArr = obsYAxisNestedArr.map((obsYAxisArr) => - formatSensorThingsApiResponseForScatterPlot(obsXAxisArr, obsYAxisArr) - ); - - // Create formatted array(s) for metadata - const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj, false) - ); - - // Extract the formatted metadata properties - const extractedFormattedDatastreamProperties = - extractPropertiesFromFormattedDatastreamMetadata( - formattedMetadataNestedArr, - false - ); - - drawScatterPlotHighcharts( - formattedObservationsArr, - extractedFormattedDatastreamProperties - ); - } catch (err) { - console.error(err); - } +const styleBuildingsDataPointsDropDown = function () { + // Create our dropdown list using `vanillaSelectBox`; supports the selection of multiple options + new vanillaSelectBox("#drop-down--bldg-data-point", { + "disableSelectAll": true, + "maxSelect": 5, + "placeHolder": "--Select--", + "search": false, + }); }; /** - * Test drawing of line chart with multiple series + * Use the `vanillaDropDown` library to style the aggregation type drop down list + * + * @returns {undefined} */ -const testLineChartMultipleSeries = async function () { - try { - const sensorsOfInterestNestedArr = [ - ["225", "vl", "60min"], - ["125", "rl", "60min"], - ["weather_station_521", "outside_temp", "60min"], - ]; - - const observationsPlusMetadataArr = - await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( - BASE_URL, - QUERY_PARAMS_COMBINED, - sensorsOfInterestNestedArr - ); - - // Extract the observations and metadata arrays of arrays - const [observationsNestedArr, metadataNestedArr] = - observationsPlusMetadataArr; - - // 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 - ); - - drawLineChartHighcharts( - formattedObservationsNestedArr, - extractedFormattedDatastreamProperties - ); - } catch (err) { - console.error(err); - } +const styleAggregationDropDown = function () { + // Create our dropdown list using `vanillaSelectBox` + new vanillaSelectBox("#drop-down--aggregation-type", { + "disableSelectAll": true, + "maxSelect": 1, + "placeHolder": "--Select--", + "search": false, + }); }; /** - * Test drawing of column chart using aggregation / sum result - monthly + * Use the `vanillaDropDown` library to style the third sampling rate down list + * + * @returns {undefined} */ -const drawColumnChartMonthlySumTest = 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) - ); - - // Unique calendar months - const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map( - (uniqueCalendarDatesArr) => - extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr) - ); - - // Calculate sum of values of observations - monthly - const observationsSumMonthlyNestedArr = - calculateSumOfObservationValuesWithinInterval( - observationsNestedArr, - "60min", - uniqueCalendarMonthsNestedArr, - "monthly" - ); - - // Format the observations - const formattedObservationsSumMonthlyNestedArr = - observationsSumMonthlyNestedArr.map((obsSumMonthlyArr, i) => - formatAggregationResultForColumnChart( - uniqueCalendarMonthsNestedArr[i], - obsSumMonthlyArr - ) - ); - - // Format the metadata - const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) - ); - - // Extract the formatted metadata properties - const extractedFormattedDatastreamProperties = - extractPropertiesFromFormattedDatastreamMetadata( - formattedMetadataNestedArr, - true, - "monthly", - "sum" - ); - - drawColumnChartHighcharts( - formattedObservationsSumMonthlyNestedArr, - extractedFormattedDatastreamProperties - ); - } catch (err) { - console.error(err); - } +const styleSamplingRateDropDown = function () { + // Create our dropdown list using `vanillaSelectBox` + new vanillaSelectBox("#drop-down--sampling-rate", { + "disableSelectAll": true, + "maxSelect": 1, + "placeHolder": "--Select--", + "search": false, + }); }; /** - * Test drawing of column chart using aggregation / sum result - daily + * Use the `vanillaDropDown` library to style the chart type drop down list + * + * @returns {undefined} */ -const drawColumnChartDailySumTest = 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 sum of values of observations - daily - const observationsSumDailyNestedArr = - calculateSumOfObservationValuesWithinInterval( - observationsNestedArr, - "60min", - uniqueCalendarDatesNestedArr, - "daily" - ); - - // Format the observations - daily - const formattedObservationsSumDailyNestedArr = - observationsSumDailyNestedArr.map((obsSumDailyArr, i) => - formatAggregationResultForColumnChart( - uniqueCalendarDatesNestedArr[i], - obsSumDailyArr - ) - ); - - // Format the metadata - const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) - ); - - // Extract the formatted metadata properties - const extractedFormattedDatastreamProperties = - extractPropertiesFromFormattedDatastreamMetadata( - formattedMetadataNestedArr, - true, - "daily", - "sum" - ); - - drawColumnChartHighcharts( - formattedObservationsSumDailyNestedArr, - extractedFormattedDatastreamProperties - ); - } catch (err) { - console.error(err); - } +const styleChartTypeDropDown = function () { + // Create our dropdown list using `vanillaSelectBox` + new vanillaSelectBox("#drop-down--chart-type", { + "disableSelectAll": true, + "maxSelect": 1, + "placeHolder": "--Select--", + "search": false, + }); }; /** - * Test drawing of column chart using raw observations + * Callback function that wraps the logic of populating the linked drop down lists. + * Will run on `DOMContentLoaded` event + * + * @returns {undefined} */ -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); - } +const afterDocumentLoads = function () { + styleBuildingsDataPointsDropDown(); + styleAggregationDropDown(); + styleSamplingRateDropDown(); + styleChartTypeDropDown(); }; /** - * Test drawing of line chart using aggregation / average result - monthly + * Callback function that draws a chart using options from the drop-down list that + * have been selected by a user. + * Will be run when the user clicks a button + * + * @async + * @returns {undefined} undefined */ -const drawLineChartMonthlyAverageTest = async function () { +const drawChartUsingSelectedOptions = async function () { try { - const sensorsOfInterestNestedArr = [ - ["125", "vl", "60min"], - ["225", "vl", "60min"], - ]; + const selectedOptionsAllDropDownLists = + getSelectedOptionsFromAllDropDownLists(); - const observationsPlusMetadata = - await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( - BASE_URL, - QUERY_PARAMS_COMBINED, - sensorsOfInterestNestedArr - ); + // Note: The aggregation type + duration and chart type are the second and + // fourth elements respectively, we have ignored the first and third elements + const [, selectedAggregationTypeDurationArr, , selectedChartTypeArr] = + selectedOptionsAllDropDownLists; - // 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) - ); - - // Unique calendar months - const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map( - (uniqueCalendarDatesArr) => - extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr) - ); - - // Calculate average of values of observations - monthly - const observationsAverageMonthlyNestedArr = - calculateAverageOfObservationValuesWithinInterval( - observationsNestedArr, - "60min", - uniqueCalendarMonthsNestedArr, - "monthly" + // Create an array of aggregation type and duration + const selectedAggregationTypeDurationSplitNestedArr = + splitMultipleOptionsTextDelimitedBySlash( + selectedAggregationTypeDurationArr ); - // Format the observations - const formattedObservationsAverageMonthlyNestedArr = - observationsAverageMonthlyNestedArr.map((obsAverageMonthlyArr, i) => - formatAggregationResultForColumnChart( - uniqueCalendarMonthsNestedArr[i], - obsAverageMonthlyArr - ) - ); + // Separate the aggregation type and the aggregation duration strings + const [selectedAggregationTypeDurationSplitArr] = + selectedAggregationTypeDurationSplitNestedArr; - // Format the metadata - const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) - ); + const [selectedAggregationTypeArr, selectedAggregationDuration] = + selectedAggregationTypeDurationSplitArr; - // Extract the formatted metadata properties - const extractedFormattedDatastreamProperties = - extractPropertiesFromFormattedDatastreamMetadata( - formattedMetadataNestedArr, - true, - "monthly", - "average" + // Array of building(s) + data point(s) + sampling rate + const selectedBuildingsDataPointsSamplingRateAbbrev = + getAbbreviationsForSelectedOptionsFromAllDropDownLists( + selectedOptionsAllDropDownLists ); - drawLineChartHighcharts( - formattedObservationsAverageMonthlyNestedArr, - extractedFormattedDatastreamProperties - ); - } catch (err) { - console.error(err); - } -}; - -/** - * Test drawing of line chart using aggregation / average result - daily - */ -const drawLineChartDailyAverageTest = async function () { - try { - const sensorsOfInterestNestedArr = [ - ["125", "vl", "60min"], - ["225", "vl", "60min"], + // Create copies of the arrays of building(s) + data point(s) + sampling rate + const selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy = [ + ...selectedBuildingsDataPointsSamplingRateAbbrev, ]; - 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 observationsAverageDailyNestedArr = - calculateAverageOfObservationValuesWithinInterval( - observationsNestedArr, - "60min", - uniqueCalendarDatesNestedArr, - "daily" - ); - - // Format the observations - daily - const formattedObservationsAverageDailyNestedArr = - observationsAverageDailyNestedArr.map((obsAverageDailyArr, i) => - formatAggregationResultForColumnChart( - uniqueCalendarDatesNestedArr[i], - obsAverageDailyArr - ) - ); - - // Format the metadata - const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) - ); - - // Extract the formatted metadata properties - const extractedFormattedDatastreamProperties = - extractPropertiesFromFormattedDatastreamMetadata( - formattedMetadataNestedArr, - true, - "daily", - "average" - ); - - drawLineChartHighcharts( - formattedObservationsAverageDailyNestedArr, - extractedFormattedDatastreamProperties - ); - } catch (err) { - console.error(err); - } -}; - -/** - * Test drawing of line chart using aggregation / minimum result - daily - */ -const drawLineChartDailyMinTest = async function () { - try { - const sensorsOfInterestNestedArr = [ - ["125", "vl", "60min"], - ["225", "vl", "60min"], + const selectedBuildingsDataPointsSamplingRateAbbrevComputedCopy = [ + ...selectedBuildingsDataPointsSamplingRateAbbrev, ]; - 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 + // Check if we have non-computed + const selectedBuildingsDataPointsSamplingRateAbbrevNonComputed = + checkIfSelectedOptionsContainTemperatureDifference( + selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy ) - ); - - // Unique calendar dates - const uniqueCalendarDatesNestedArr = observationsNestedArr.map( - (observationsArr) => - extractUniqueCalendarDatesFromTimestamp(observationsArr) - ); - - // Calculate average of values of observations - daily - const observationsMinimumDailyNestedArr = - calculateMinimumObservationValuesWithinInterval( - observationsNestedArr, - "60min", - uniqueCalendarDatesNestedArr, - "daily" - ); + ? deleteTemperatureDifferenceOptions( + selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy + ) + : selectedBuildingsDataPointsSamplingRateAbbrevNonComputedCopy; + + // Check if we have computed / dT + const selectedBuildingsDataPointsSamplingRateAbbrevComputed = + checkIfSelectedOptionsContainTemperatureDifference( + selectedBuildingsDataPointsSamplingRateAbbrevComputedCopy + ) + ? extractTemperatureDifferenceOptions( + selectedBuildingsDataPointsSamplingRateAbbrevComputedCopy + ) + : []; + + // Display the loading indicator + showLoadingSpinner(); + + // Fetch the observations + metadata / non-computed + const observationsPlusMetadataNonComputed = + selectedBuildingsDataPointsSamplingRateAbbrevNonComputed.length === 0 + ? [[], []] + : await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( + BASE_URL, + QUERY_PARAMS_COMBINED, + selectedBuildingsDataPointsSamplingRateAbbrevNonComputed + ); + + // Fetch the observations + metadata / computed (dT) + const observationsPlusMetadataComputed = + selectedBuildingsDataPointsSamplingRateAbbrevComputed.length === 0 + ? [[], []] + : await calculateVorlaufMinusRuecklaufTemperature( + BASE_URL, + QUERY_PARAMS_COMBINED, + extractBuildingPlusSamplingRate( + selectedBuildingsDataPointsSamplingRateAbbrevComputed + ) + ); + + // Extract the combined arrays for observations and metadata / non-computed + const [observationsNestedNonComputedArr, metadataNestedNonComputedArr] = + observationsPlusMetadataNonComputed; + + // Extract the combined arrays for observations and metadata / computed (dT) + const [observationsNestedComputedArr, metadataNestedComputedArr] = + observationsPlusMetadataComputed; + + // Create a combined array of observations and metadata + const observationsPlusMetadataCombined = [ + [...observationsNestedNonComputedArr, ...observationsNestedComputedArr], + [...metadataNestedNonComputedArr, ...metadataNestedComputedArr], + ]; - // Format the observations - daily - const formattedObservationsMinimumDailyNestedArr = - observationsMinimumDailyNestedArr.map((obsMinDailyArr, i) => - formatAggregationResultForColumnChart( - uniqueCalendarDatesNestedArr[i], - obsMinDailyArr - ) - ); + const [observationsComboNestedArr, metadataComboNestedArr] = + observationsPlusMetadataCombined; - // Format the metadata - const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) + // Create formatted array(s) for metadata - used by ALL chart types + const formattedMetadataNestedArr = metadataComboNestedArr.map( + (metadataObj) => formatDatastreamMetadataForChart(metadataObj) ); - // Extract the formatted metadata properties + // Extract the formatted metadata properties - used by ALL chart types const extractedFormattedDatastreamProperties = extractPropertiesFromFormattedDatastreamMetadata( formattedMetadataNestedArr, - true, - "daily", - "minimum" + false ); - drawLineChartHighcharts( - formattedObservationsMinimumDailyNestedArr, - extractedFormattedDatastreamProperties - ); - } catch (err) { - console.error(err); - } -}; + // The formatted abbreviations array is nested + const [selectedBuildingsDataPointsSamplingRateAbbrevArr] = + selectedBuildingsDataPointsSamplingRateAbbrev; -/** - * Test drawing of line chart using aggregation / maximum result - daily - */ -const drawLineChartMonthlyMaxTest = async function () { - try { - const sensorsOfInterestNestedArr = [ - ["125", "vl", "60min"], - ["225", "vl", "60min"], - ]; + // Extract the formatted sampling rate string - used by ALL chart types + const [, , selectedSamplingRateAbbrev] = + selectedBuildingsDataPointsSamplingRateAbbrevArr; - const observationsPlusMetadata = - await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( - BASE_URL, - QUERY_PARAMS_COMBINED, - sensorsOfInterestNestedArr - ); + // User-specified start date and end date for aggregation - used by MULTIPLE chart types + const aggregationStartDate = "2020-01-01"; + const aggregationEndDate = "2020-12-31"; - // 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 - ) + // Extract observations within the user-specified start and end date - used by MULTIPLE chart types + const observationsAggregationNestedArr = observationsComboNestedArr.map( + (obsArr) => + extractObservationsWithinDatesInterval( + obsArr, + "60min", + aggregationStartDate, + aggregationEndDate + ) ); - // Unique calendar dates - const uniqueCalendarDatesNestedArr = observationsNestedArr.map( + // Unique calendar dates - used by MULTIPLE chart types + const uniqueCalendarDatesNestedArr = observationsAggregationNestedArr.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 - ); + selectedChartTypeArr.forEach((selectedChartType) => { + if (selectedChartType === "Heatmap") { + // We are interested in raw observations + if ( + checkIfChartRequiresRawObservations( + selectedAggregationTypeArr, + selectedAggregationDuration + ) + ) { + drawHeatmapBasedOnSelectedOptions( + selectedBuildingsDataPointsSamplingRateAbbrev, + observationsComboNestedArr, + extractedFormattedDatastreamProperties + ); + } else { + throw new Error( + "This type of chart (Heatmap) does not support aggregated results" + ); + } + } + if (selectedChartType === "Scatter Plot") { + // We are interested in raw observations + if ( + checkIfChartRequiresRawObservations( + selectedAggregationTypeArr, + selectedAggregationDuration + ) + ) { + drawScatterPlotFromChartSelection( + selectedBuildingsDataPointsSamplingRateAbbrev, + observationsComboNestedArr, + extractedFormattedDatastreamProperties + ); + } else { + throw new Error( + "This type of chart (Scatter Plot) does not support aggregated results" + ); + } + } + if (selectedChartType === "Line") { + // We are interested in raw observations or aggregated observations + + // Raw observations + if (selectedAggregationTypeArr === "None (raw data)") { + // Create formatted array(s) for observations + const formattedRawObservationsLineChartNestedArr = + observationsComboNestedArr.map((observationsArr) => + formatSensorThingsApiResponseForLineOrColumnChart(observationsArr) + ); + + drawLineChartHighcharts( + formattedRawObservationsLineChartNestedArr, + extractedFormattedDatastreamProperties + ); + } + // Aggregated observations + else { + drawLineChartBasedOnSelectedAggregationOptions( + selectedAggregationTypeArr, + selectedAggregationDuration, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarDatesNestedArr, + formattedMetadataNestedArr + ); + } + } + if (selectedChartType === "Column") { + // We are interested in raw observations or aggregated observations + + // Raw observations + if (selectedAggregationTypeArr === "None (raw data)") { + // Create formatted array(s) for observations + const formattedRawObservationsColumnChartNestedArr = + observationsComboNestedArr.map((observationsArr) => + formatSensorThingsApiResponseForLineOrColumnChart(observationsArr) + ); + + drawColumnChartHighcharts( + formattedRawObservationsColumnChartNestedArr, + extractedFormattedDatastreamProperties + ); + } + // Aggregated observations + else { + drawColumnChartBasedOnSelectedAggregationOptions( + selectedAggregationTypeArr, + selectedAggregationDuration, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarDatesNestedArr, + formattedMetadataNestedArr + ); + } + } + }); } catch (err) { console.error(err); + } finally { + // Hide the loading indicator + hideLoadingSpinner(); } }; -// drawScatterPlotHCTest2(); -// drawHeatmapHCUsingTempDifference(); -// testLineChartMultipleSeries(); -// drawColumnChartMonthlySumTest(); -// drawColumnChartDailySumTest(); -// drawColumnChartNonAggregationTest(); -// drawLineChartMonthlyAverageTest(); -// drawLineChartDailyAverageTest(); -// drawLineChartDailyMinTest(); -// drawLineChartMonthlyMaxTest(); +document.addEventListener("DOMContentLoaded", afterDocumentLoads); + +document + .querySelector("#btn-draw-chart") + .addEventListener("click", drawChartUsingSelectedOptions); diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js deleted file mode 100644 index b3526dec37b3f890434f087c7ff1103f3d03414c..0000000000000000000000000000000000000000 --- a/public/js/dropDownList.js +++ /dev/null @@ -1,417 +0,0 @@ -"use strict"; - -import { - BASE_URL, - QUERY_PARAMS_COMBINED, -} from "./src_modules/baseUrlPlusQueryParams.mjs"; - -import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs"; - -import { - formatDatastreamMetadataForChart, - extractPropertiesFromFormattedDatastreamMetadata, -} from "./src_modules/fetchedDataProcessing.mjs"; - -import { - formatSensorThingsApiResponseForLineOrColumnChart, - drawLineChartHighcharts, -} from "./src_modules/chartLine.mjs"; - -import { - formatSensorThingsApiResponseForHeatMap, - drawHeatMapHighcharts, -} from "./src_modules/chartHeatmap.mjs"; - -import { - showLoadingSpinner, - hideLoadingSpinner, -} from "./src_modules/loadingIndicator.mjs"; - -import { vanillaSelectBox } from "./thirdparty/vanillaSelectBox.mjs"; - -/** - * Use the `vanillaDropDown` library to style the first level drop down list - * - * @returns {undefined} - */ -const styleLevelOneDropDown = function () { - // Create our dropdown list using `vanillaSelectBox`; supports the selection of multiple options - new vanillaSelectBox("#drop-down--bldg-data-point", { - "disableSelectAll": true, - "maxSelect": 5, - "placeHolder": "--Select--", - "search": false, - }); -}; - -/** - * Use the `vanillaDropDown` library to style the second level drop down list - * - * @returns {undefined} - */ -const styleLevelTwoDropDown = function () { - // Create our dropdown list using `vanillaSelectBox` - new vanillaSelectBox("#drop-down--aggregation-type", { - "disableSelectAll": true, - "maxSelect": 1, - "placeHolder": "--Select--", - "search": false, - }); -}; - -/** - * Use the `vanillaDropDown` library to style the third level drop down list - * - * @returns {undefined} - */ -const styleLevelThreeDropDown = function () { - // Create our dropdown list using `vanillaSelectBox` - new vanillaSelectBox("#drop-down--sampling-rate", { - "disableSelectAll": true, - "maxSelect": 1, - "placeHolder": "--Select--", - "search": false, - }); -}; - -/** - * Use the `vanillaDropDown` library to style the fourth level drop down list - * - * @returns {undefined} - */ -const styleLevelFourDropDown = function () { - // Create our dropdown list using `vanillaSelectBox` - new vanillaSelectBox("#drop-down--chart-type", { - "disableSelectAll": true, - "maxSelect": 1, - "placeHolder": "--Select--", - "search": false, - }); -}; - -/** - * Callback function that wraps the logic of populating the linked drop down lists. - * Will run on `DOMContentLoaded` event - * - * @returns {undefined} - */ -const afterDocumentLoads = function () { - styleLevelOneDropDown(); - styleLevelTwoDropDown(); - styleLevelThreeDropDown(); - styleLevelFourDropDown(); -}; - -/** - * Get the selected option(s) from a dropdown list - * - * @param {String} selectorStr A CSS selector string representing the dropdown list - * @returns {Array} An array of string(s) representing the value(s) of the selected `<option>` elements - */ -const getSelectedOptionsFromDropDownList = function (selectorStr) { - // Array to store our final result - const selectedOptionsArr = []; - - // Select all the matching <option> elements as a NodeList - const optionElements = document.querySelectorAll(`${selectorStr} option`); - - optionElements.forEach((optionEl) => { - if (optionEl.selected) { - selectedOptionsArr.push(optionEl.value); - } - }); - - return selectedOptionsArr; -}; - -/** - * Process the selected option(s) from a buildings & data points dropdown list. - * This is currently the first dropdown list in the UI. - * - * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements - * @returns {Array} An array of string(s) representing the processed value(s) of the selected buildings & data points option(s) - */ -const processSelectionsFromBuildingDataPointOptions = function ( - selectedOptionsArr -) { - // Array to store our final result - const selectedOptionsBuildingDataPointArr = []; - - selectedOptionsArr.forEach((optionStr) => { - if (optionStr.includes("/")) { - // Case 1: <option> element's value CONTAINS a "/" character - // We wish to create a string like this `Bau 101/VL` - - // Split the <option> element's value into two substrings - const optionsStrPartOne = optionStr.slice(0, 3); - const optionsStrPartTwo = optionStr.slice(3); - - // Create a new string for the first substring - const optionsStrPartOneNew = `Bau ${optionsStrPartOne}`; - - // Create a new combined string - const optionsStrNew = optionsStrPartOneNew + optionsStrPartTwo; - - selectedOptionsBuildingDataPointArr.push(optionsStrNew); - } else { - // Case 2: <option> element's value DOES NOT CONTAIN a "/" character - // We wish to create a string like this `Other/Außentemp` - selectedOptionsBuildingDataPointArr.push(`Other/${optionStr}`); - } - }); - - return selectedOptionsBuildingDataPointArr; -}; - -/** - * Split an option element's value (a string) using a forward slash character ("/") as the delimiter - * - * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements - * @returns {Array} An array made up of resulting strings after splitting - */ -const splitOptionsTextDelimitedBySlash = function (selectedOptionsArr) { - return selectedOptionsArr.map((selectedOption) => selectedOption.split("/")); -}; - -/** - * Get the values from the currently selected options in ALL the drop down lists - * @returns {Array} An array containing four arrays, where each array contains the values of the selected options - */ -const getSelectedOptionsFromAllDropDownLists = function () { - const selectedBuildingDataPointOptionsArr = - processSelectionsFromBuildingDataPointOptions( - getSelectedOptionsFromDropDownList("#drop-down--bldg-data-point") - ); - - // Separate the building ID from the data point - const selectedBuildingDataPointOptionsSplitArr = - splitOptionsTextDelimitedBySlash(selectedBuildingDataPointOptionsArr); - - const selectedAggregationOptionsArr = getSelectedOptionsFromDropDownList( - "#drop-down--aggregation-type" - ); - - const selectedSamplingRateArr = getSelectedOptionsFromDropDownList( - "#drop-down--sampling-rate" - ); - - const selectedChartTypeArr = getSelectedOptionsFromDropDownList( - "#drop-down--chart-type" - ); - - // Ensure that all the options have at least one selection - - if ( - selectedBuildingDataPointOptionsSplitArr.length === 0 || - selectedAggregationOptionsArr.length === 0 || - selectedSamplingRateArr.length === 0 || - selectedChartTypeArr.length === 0 - ) - return; - - return [ - selectedBuildingDataPointOptionsSplitArr, - selectedAggregationOptionsArr, - selectedSamplingRateArr, - selectedChartTypeArr, - ]; -}; - -/** - * Get the abbreviated form of building IDs, phenomenon names and sensor sampling rates - * @param {String} buildingFullForm A string representation of the full form of a building ID - * @param {String} phenomenonFullForm A string representation of the full form of a phenomenon name - * @param {String} samplingRateFullForm A string representation of the full form of a sensor's sampling rate - * @returns {Array} An array of abbreviated strings - */ -const getBuildingSensorSamplingRateAbbreviation = function ( - buildingFullForm, - phenomenonFullForm, - samplingRateFullForm -) { - const fullFormToAbbreviationMapping = { - buildings: { - "Bau 101": "101", - "Bau 102": "102", - "Bau 107": "107", - "Bau 112": "112, 118", - "Bau 125": "125", - "Bau 225": "225", - "Other": "weather_station_521", - }, - phenomenon: { - VL: "vl", - RL: "rl", - dT: "dT", - - Durchfluss: "flow", - Leistung: "power", - Energie: "energy", - Energie_VERBR: "energy_verb", - Außentemp: "outside_temp", - }, - samplingRate: { - "15 min": "15min", - "60 min": "60min", - }, - }; - - if ( - fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm] === undefined - ) - throw new Error( - "The provided building ID is not valid or is not supported by this function" - ); - - if ( - fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm] === - undefined - ) - throw new Error( - "The provided data point is not valid or is not supported by this function" - ); - - if ( - fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm] === - undefined - ) - throw new Error( - "The provided sampling rate is not valid or is not supported by this function" - ); - - const buildingAbbrev = - fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm]; - - const phenomenonAbbrev = - fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm]; - - const samplingRateAbbrev = - fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm]; - - return [buildingAbbrev, phenomenonAbbrev, samplingRateAbbrev]; -}; - -/** - * Get the abbreviated form for the currently selected options in ALL the drop down lists - * - * @param {Array} allSelectedOptionsArr An array containing four arrays, where each array contains the values of the selected options - * @returns {Array} An array which contains one or more nested arrays of abbreviations of building(s), data point(s) and sampling rate(s) - */ -const getAbbreviationsForSelectedOptionsFromAllDropDownLists = function ( - allSelectedOptionsArr -) { - // Note: The sampling rate array is the third array, therefore we skip one element - const [selectedBuildingDataPointOptionsSplitArr, , selectedSamplingRateArr] = - allSelectedOptionsArr; - - // The building is the first element - const selectedBuildingsArr = selectedBuildingDataPointOptionsSplitArr.map( - (selectedBuildingDataPoint) => selectedBuildingDataPoint[0] - ); - - // The data point is the second element - const selectedDataPointsArr = selectedBuildingDataPointOptionsSplitArr.map( - (selectedBuildingDataPoint) => selectedBuildingDataPoint[1] - ); - - // Assume that the buildings and data points arrays have equal length - // use one of the arrays for looping - if (selectedBuildingsArr.length !== selectedDataPointsArr.length) - throw new Error( - "The buildings array and data points array have different lengths" - ); - - return selectedBuildingsArr.map((selectedBuilding, i) => - getBuildingSensorSamplingRateAbbreviation( - selectedBuilding, - selectedDataPointsArr[i], - ...selectedSamplingRateArr - ) - ); -}; - -/** - * Callback function for chart selection using drop down list - * @returns {undefined} - */ -const selectChartTypeFromDropDown = async function () { - try { - const selectedOptions = getSelectedOptionsFromDropDownLists(); - - if (selectedOptions === undefined) return; - - const selectedOptionsAbbreviationsArr = - getBuildingSensorSamplingRateAbbreviation(...selectedOptions); - - const selectedChartType = document.querySelector( - "#drop-down--chart-type" - ).value; - - if (selectedChartType === "--Select--") return; - - // Display the loading indicator - showLoadingSpinner(); - - // The `getMetadataPlusObservationsFromSingleOrMultipleDatastreams` function expects a nested array structure - const abbreviationsNestedArr = [selectedOptionsAbbreviationsArr]; - - const observationsPlusMetadata = - await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( - BASE_URL, - QUERY_PARAMS_COMBINED, - abbreviationsNestedArr - ); - - // Extract the combined arrays for observations and metadata - const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata; - - // Create formatted array(s) for observations - line chart - const formattedObsLineChartNestedArr = observationsNestedArr.map( - (observationsArr) => - formatSensorThingsApiResponseForLineOrColumnChart(observationsArr) - ); - - // Create formatted array(s) for observations - heatmap - const formattedObsHeatMapNestedArr = observationsNestedArr.map( - (observationsArr) => - formatSensorThingsApiResponseForHeatMap(observationsArr) - ); - - // Create formatted array(s) for metadata - same for both chart types - const formattedMetadataArr = metadataNestedArr.map((metadataObj) => - formatDatastreamMetadataForChart(metadataObj) - ); - - // Extract the formatted metadata properties - const extractedFormattedDatastreamProperties = - extractPropertiesFromFormattedDatastreamMetadata( - formattedMetadataArr, - false - ); - - if (selectedChartType === "Line") { - drawLineChartHighcharts( - formattedObsLineChartNestedArr, - extractedFormattedDatastreamProperties - ); - } else if (selectedChartType === "Heatmap") { - // First need to extract the formatted observations from the nested array - // Heatmap only needs one set of formatted observation values - drawHeatMapHighcharts( - ...formattedObsHeatMapNestedArr, - extractedFormattedDatastreamProperties - ); - } - } catch (err) { - console.error(err); - } finally { - // Hide the loading indicator - hideLoadingSpinner(); - } -}; - -document.addEventListener("DOMContentLoaded", afterDocumentLoads); - -document - .querySelector("#drop-down--chart-type") - .addEventListener("change", selectChartTypeFromDropDown); diff --git a/public/js/src_modules/calculateTemperatureDiff.mjs b/public/js/src_modules/calculateTemperatureDiff.mjs index b429303342217684dabf1b0ca3126ad1b5b189e8..5b7c55097489f9e000ca603efb4faafe68eb4dee 100644 --- a/public/js/src_modules/calculateTemperatureDiff.mjs +++ b/public/js/src_modules/calculateTemperatureDiff.mjs @@ -7,127 +7,139 @@ import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./fe import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcessing.mjs"; /** - * Calculate the temperature difference, dT, between Vorlauf temperature [VL] and Rücklauf temperature [RL] (i.e., dT = VL - RL) + * Calculate the temperature difference, dT, between Vorlauf temperature [VL] and + * Rücklauf temperature [RL] (i.e., dT = VL - RL). In addition, create synthetic metadata + * for the temperature difference * @async * @param {String} baseUrl Base URL of the STA server * @param {Object} urlParams The URL parameters to be sent together with the GET request - * @param {String} buildingId The building ID as a string - * @param {String} samplingRate The sampling rate as a string - * @returns {Promise} A promise that contains an array (that is made up of a temperature difference array and a metadata object) when fulfilled + * @param {Array} buildingSamplingRateNestedArr A N*1 array (where N >= 1) containing a nested array of buildings & sampling rates as strings, i.e. [["101", "15min"]] or [["101", "15min"], ["102", "60min"]] or [["101", "15min"], ["102", "60min"], ["225", "60min"]], etc + * @returns {Promise} A promise that contains a 1*2 array (the first element is an array that contans N Observations arrays; and the second element is an array of N Datastream metadata objects) when fulfilled */ export const calculateVorlaufMinusRuecklaufTemperature = async function ( baseUrl, urlParams, - buildingId, - samplingRate + buildingSamplingRateNestedArr ) { try { - const bldgSensorSamplingRateNestedArr = [ - [buildingId, "vl", samplingRate], - [buildingId, "rl", samplingRate], - ]; - - const BUILDING_ID = buildingId; - const SAMPLING_RATE = samplingRate; - - const observationsPlusMetadata = - await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( - baseUrl, - urlParams, - bldgSensorSamplingRateNestedArr + // Arrays to store our results + const combinedObservationsArr = []; + const combinedMetadataArr = []; + + const buildingDataPointSamplingRateNestedTwiceArr = + buildingSamplingRateNestedArr.map((bldgSmplngRate) => { + // The building ID is the first element, sampling rate is second element + return [ + [bldgSmplngRate[0], "vl", bldgSmplngRate[1]], + [bldgSmplngRate[0], "rl", bldgSmplngRate[1]], + ]; + }); + + // Note: We have to use a for/of loop here due to the asynchronous nature of our code + for (const bldgDataPtSamplingRateNestedArr of buildingDataPointSamplingRateNestedTwiceArr) { + // Use the first element of the nested array to extract building ID + sampling rate + // Note: we skip the second element + const [buildingId, , samplingRate] = bldgDataPtSamplingRateNestedArr[0]; + + const BUILDING_ID = buildingId; + const SAMPLING_RATE = samplingRate; + + const observationsPlusMetadata = + await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( + baseUrl, + urlParams, + bldgDataPtSamplingRateNestedArr + ); + + // Extract Vorlauf temperature, Ruecklauf temperature and metadata + const [ + [vorlaufTemperatureObsArr, ruecklaufTemperatureObsArr], + [metadataVorlauf, metadataRuecklauf], + ] = observationsPlusMetadata; + + // Compare the lengths of the observations arrays for VL and RL, + // delete the unique observation(s), if necessary + const [vorlaufTemperatureObsFinalArr, ruecklaufTemperatureObsFinalArr] = + vorlaufTemperatureObsArr.length === ruecklaufTemperatureObsArr.length + ? [vorlaufTemperatureObsArr, ruecklaufTemperatureObsArr] + : checkForAndDeleteUniqueObservationsFromLargerArray( + vorlaufTemperatureObsArr, + ruecklaufTemperatureObsArr + ); + + // Extract the temperature values + const vorlaufTemperatureValues = vorlaufTemperatureObsFinalArr.map( + (vlTempObs) => vlTempObs[1] + ); + const ruecklaufTemperatureValues = ruecklaufTemperatureObsFinalArr.map( + (rlTempObs) => rlTempObs[1] ); - // Extract Vorlauf temperature, Ruecklauf temperature and metadata - const [ - [vorlaufTemperatureObsArr, ruecklaufTemperatureObsArr], - [metadataVorlauf, metadataRuecklauf], - ] = observationsPlusMetadata; - - // Compare the lengths of the observations arrays for VL and RL, - // delete the unique observation(s), if necessary - const [vorlaufTemperatureObsFinalArr, ruecklaufTemperatureObsFinalArr] = - vorlaufTemperatureObsArr.length === ruecklaufTemperatureObsArr.length - ? [vorlaufTemperatureObsArr, ruecklaufTemperatureObsArr] - : checkForAndDeleteUniqueObservationsFromLargerArray( - vorlaufTemperatureObsArr, - ruecklaufTemperatureObsArr - ); - - // Extract the temperature values - const vorlaufTemperatureValues = vorlaufTemperatureObsFinalArr.map( - (vlTempObs) => vlTempObs[1] - ); - const ruecklaufTemperatureValues = ruecklaufTemperatureObsFinalArr.map( - (rlTempObs) => rlTempObs[1] - ); - - // The arrays have equal length, we need only use one of them for looping - // Resulting array contains the following pairs (timestamp + dT) - const vorlaufMinusRuecklaufTemperatureObs = vorlaufTemperatureObsArr.map( - (vlTempObs, i) => { - // Use timestamp from VL, since is equal to that of RL - const timestamp = vlTempObs[0]; - - // Case 1: One of the observation values is `null`, - // no need to calculate temperature difference - if ( - vorlaufTemperatureValues[i] === null || - ruecklaufTemperatureValues[i] === null - ) { - return [timestamp, null]; + // The arrays have equal length, we need only use one of them for looping + // Resulting array contains the following pairs (timestamp + dT) + const vorlaufMinusRuecklaufTemperatureObs = vorlaufTemperatureObsArr.map( + (vlTempObs, i) => { + // Use timestamp from VL, since is equal to that of RL + const timestamp = vlTempObs[0]; + + // Case 1: One of the observation values is `null`, + // no need to calculate temperature difference + if ( + vorlaufTemperatureValues[i] === null || + ruecklaufTemperatureValues[i] === null + ) { + return [timestamp, null]; + } + + // Case 2: Neither of the observation values is `null`, + // calculate temperature difference + return [ + timestamp, + vorlaufTemperatureValues[i] - ruecklaufTemperatureValues[i], + ]; } + ); - // Case 2: Neither of the observation values is `null`, - // calculate temperature difference - return [ - timestamp, - vorlaufTemperatureValues[i] - ruecklaufTemperatureValues[i], - ]; - } - ); - - // From Vorlauf metadata, extract `name` and `unitOfMeasurement` - const { - name: datastreamNameVorlauf, - unitOfMeasurement: unitOfMeasurementVorlauf, - } = metadataVorlauf; - - // From Ruecklauf metadata, extract `name` - const { name: datastreamNameRuecklauf } = metadataRuecklauf; - - // Extract the phenomenon names from the Datastream names - const phenomenonNameVorlauf = extractPhenomenonNameFromDatastreamName( - datastreamNameVorlauf - ); - const phenomenonNameRuecklauf = extractPhenomenonNameFromDatastreamName( - datastreamNameRuecklauf - ); - - // Create our custom datastream description text - // The resulting datastream description string has two `temperature` substrings; - // replace the first occurence with an empty string - const descriptionTempDifference = - `Computed dT: ${phenomenonNameVorlauf} minus ${phenomenonNameRuecklauf}`.replace( - "temperature", - "" + // From Vorlauf metadata, extract `name` and `unitOfMeasurement` + const { + name: datastreamNameVorlauf, + unitOfMeasurement: unitOfMeasurementVorlauf, + } = metadataVorlauf; + + // From Ruecklauf metadata, extract `name` + const { name: datastreamNameRuecklauf } = metadataRuecklauf; + + // Extract the phenomenon names from the Datastream names + const phenomenonNameVorlauf = extractPhenomenonNameFromDatastreamName( + datastreamNameVorlauf + ); + const phenomenonNameRuecklauf = extractPhenomenonNameFromDatastreamName( + datastreamNameRuecklauf ); - // Create our custom datastream name text - const nameTempDifference = `BOSCH_${BUILDING_ID} / dT Temperature difference (VL-RL) DS:${SAMPLING_RATE}`; - - // The datastream object that we return needs to have these property names - const description = descriptionTempDifference; - const name = nameTempDifference; - const unitOfMeasurement = unitOfMeasurementVorlauf; - - return [ - vorlaufMinusRuecklaufTemperatureObs, - { - description, - name, - unitOfMeasurement, - }, - ]; + // Create our custom datastream description text + // The resulting datastream description string has two `temperature` substrings; + // replace the first occurence with an empty string + const descriptionTempDifference = + `Computed dT: ${phenomenonNameVorlauf} minus ${phenomenonNameRuecklauf}`.replace( + "temperature", + "" + ); + + // Create our custom datastream name text + const nameTempDifference = `BOSCH_${BUILDING_ID} / dT Temperature difference (VL-RL) DS:${SAMPLING_RATE}`; + + // The datastream object that we return needs to have these property names + const description = descriptionTempDifference; + const name = nameTempDifference; + const unitOfMeasurement = unitOfMeasurementVorlauf; + + // Add the observations and metadata to our arrays + combinedObservationsArr.push(vorlaufMinusRuecklaufTemperatureObs); + combinedMetadataArr.push({ description, name, unitOfMeasurement }); + } + + return [combinedObservationsArr, combinedMetadataArr]; } catch (err) { console.error(err); } diff --git a/public/js/src_modules/dropDownListHelpers.mjs b/public/js/src_modules/dropDownListHelpers.mjs new file mode 100644 index 0000000000000000000000000000000000000000..d70d7143c7436127589cf13008e75666b18783e3 --- /dev/null +++ b/public/js/src_modules/dropDownListHelpers.mjs @@ -0,0 +1,422 @@ +/** + * Get the selected option(s) from a dropdown list + * + * @param {String} selectorStr A CSS selector string representing the dropdown list + * @returns {Array} An array of string(s) representing the value(s) of the selected `<option>` elements + */ +const getSelectedOptionsFromDropDownList = function (selectorStr) { + // Array to store our final result + const selectedOptionsArr = []; + + // Select all the matching <option> elements as a NodeList + const optionElements = document.querySelectorAll(`${selectorStr} option`); + + optionElements.forEach((optionEl) => { + if (optionEl.selected) { + selectedOptionsArr.push(optionEl.value); + } + }); + + return selectedOptionsArr; +}; + +/** + * Process the selected option(s) from a buildings & data points dropdown list. + * + * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements + * @returns {Array} An array of string(s) representing the processed value(s) of the selected buildings & data points option(s) + */ +const processSelectionsFromBuildingDataPointOptions = function ( + selectedOptionsArr +) { + // Array to store our final result + const selectedOptionsBuildingDataPointArr = []; + + selectedOptionsArr.forEach((optionStr) => { + // Case 1: <option> element's value CONTAINS a "/" character + // We wish to create a string like this `Bau 101/VL` + if (optionStr.includes("/")) { + // Split the <option> element's value into two substrings + const optionsStrPartOne = optionStr.slice(0, 3); + const optionsStrPartTwo = optionStr.slice(3); + + // Create a new string for the first substring + const optionsStrPartOneNew = `Bau ${optionsStrPartOne}`; + + // Create a new combined string + const optionsStrNew = optionsStrPartOneNew + optionsStrPartTwo; + + selectedOptionsBuildingDataPointArr.push(optionsStrNew); + } + // Case 2: <option> element's value DOES NOT CONTAIN a "/" character + // We wish to create a string like this `Other/Außentemp` + else { + selectedOptionsBuildingDataPointArr.push(`Other/${optionStr}`); + } + }); + + return selectedOptionsBuildingDataPointArr; +}; + +/** + * Split an option element's value (a string) using a forward slash character ("/") as the delimiter + * + * @param {String} selectedOptionsStr A string representing the value of the selected `<option>` element + * @returns {String} Resulting strings after splitting + */ +const splitOptionsTextDelimitedBySlash = function (selectedOptionsStr) { + return selectedOptionsStr.split("/"); +}; + +/** + * Split an array of option element's values (strings) which have a forward slash character ("/") as the delimiter + * + * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements + * @returns {Array} An array made up of resulting strings after splitting + */ +const splitMultipleOptionsTextDelimitedBySlash = function (selectedOptionsArr) { + return selectedOptionsArr.map((selectedOption) => + splitOptionsTextDelimitedBySlash(selectedOption) + ); +}; + +/** + * Get the values from the currently selected options in ALL the drop down lists + * @returns {Array} An array containing four arrays, where each array contains the values of the selected options + */ +const getSelectedOptionsFromAllDropDownLists = function () { + const selectedBuildingDataPointOptionsArr = + processSelectionsFromBuildingDataPointOptions( + getSelectedOptionsFromDropDownList("#drop-down--bldg-data-point") + ); + + // Separate the building ID from the data point + const selectedBuildingDataPointOptionsSplitArr = + splitMultipleOptionsTextDelimitedBySlash( + selectedBuildingDataPointOptionsArr + ); + + const selectedAggregationOptionsArr = getSelectedOptionsFromDropDownList( + "#drop-down--aggregation-type" + ); + + const selectedSamplingRateArr = getSelectedOptionsFromDropDownList( + "#drop-down--sampling-rate" + ); + + const selectedChartTypeArr = getSelectedOptionsFromDropDownList( + "#drop-down--chart-type" + ); + + // Ensure that all the options have at least one selection + if ( + selectedBuildingDataPointOptionsSplitArr.length === 0 || + selectedAggregationOptionsArr.length === 0 || + selectedSamplingRateArr.length === 0 || + selectedChartTypeArr.length === 0 + ) + return; + + return [ + selectedBuildingDataPointOptionsSplitArr, + selectedAggregationOptionsArr, + selectedSamplingRateArr, + selectedChartTypeArr, + ]; +}; + +/** + * Check whether the abbreviated buildings + data points + sampling rate strings + * contain the temperature difference [dT] between Vorlauf temperature [VL] and + * Rücklauf temperature [RL] (i.e., dT = VL - RL). Unlike all the other data points, + * this data point is computed in a separate step + * + * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of strings representing the abbreviated building + data point + sampling rate values + * @returns {Boolean} true if the selected options contain the string `dT`, false otherwise + */ +const checkIfSelectedOptionsContainTemperatureDifference = function ( + buildingDataPointSamplingRateAbbrevArr +) { + // Create a flattened copy of our input array, + // then check if it contains the string `dT` + return buildingDataPointSamplingRateAbbrevArr.flat().includes("dT"); +}; + +/** + * Get the index(es) of the the abbreviated buildings + data points + sampling rate string(s) + * that contains the temperature difference (dT) + * + * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s) + * @returns {Array} An array that contains an integer(s) whose value(s) are the index(es) of the abbreviated building + data point + sampling rate string(s) containing the temperature difference (dT) + */ +const getIndexesOfTemperatureDifferenceOptions = function ( + buildingDataPointSamplingRateAbbrevArr +) { + // An array to store the final result + const foundIndexesArr = []; + + // Use the index, i, provided by `forEach` array method + buildingDataPointSamplingRateAbbrevArr.forEach( + (bldgDataPntSamplingRateAbbrvArr, i) => { + if (bldgDataPntSamplingRateAbbrvArr.includes("dT")) { + foundIndexesArr.push(i); + } + } + ); + + return foundIndexesArr; +}; + +/** + * Delete the abbreviated building + data point + sampling rate string(s) that contains the temperature difference (dT) + * + * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s) + * @returns {Array} An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s) + */ +const deleteTemperatureDifferenceOptions = function ( + buildingDataPointSamplingRateAbbrevArr +) { + // Calculate the index(es) that we wish to delete + const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions( + buildingDataPointSamplingRateAbbrevArr + ); + + // Delete the index(es) of `dT`, modifies the array in place + // Note: The resulting array is sparse + foundIndexesArr.forEach( + (foundIndex) => delete buildingDataPointSamplingRateAbbrevArr[foundIndex] + ); + + // Array to store our final result + const buildingDataPointFinalArr = []; + + // Remove the empty sub array(s) that makes entire array sparse + // Note: `empty` does not mean `undefined` or `null` + buildingDataPointSamplingRateAbbrevArr.forEach( + (bldgDataPntSmplingRateAbbrvArr) => { + if (typeof bldgDataPntSmplingRateAbbrvArr === "object") { + buildingDataPointFinalArr.push(bldgDataPntSmplingRateAbbrvArr); + } + } + ); + + return buildingDataPointFinalArr; +}; + +/** + * Extract the abbreviated building + data point + sampling rate string(s) that contains the temperature difference (dT) + * + * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s) + * @returns {Array} An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s) + */ +const extractTemperatureDifferenceOptions = function ( + buildingDataPointSamplingRateAbbrevArr +) { + // Array to store final result + const temperatureDifferenceOptionsAbbrevArr = []; + + // Calculate the index(es) that we wish to extract + const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions( + buildingDataPointSamplingRateAbbrevArr + ); + + foundIndexesArr.forEach((foundIndex) => { + // Extracted array for a single found index + const bldgDataPntSamplingRateAbbrvArr = + buildingDataPointSamplingRateAbbrevArr[foundIndex]; + + // Extract the building and sampling rate strings + // Note: we have ignored the second element + // const [bldgAbbrv, , samplingRateAbbrv] = bldgDataPntSamplingRateAbbrvArr; + + // Create a new array that contains two elements, + // the building and sampling rate abbreviated strings + // const bldgSamplingRateAbbrvArr = [bldgAbbrv, samplingRateAbbrv]; + + temperatureDifferenceOptionsAbbrevArr.push(bldgDataPntSamplingRateAbbrvArr); + }); + + return temperatureDifferenceOptionsAbbrevArr; +}; + +/** + * Extract the abbreviated building + sampling rate string(s) for use in calculating the temperature difference (dT = VL - RL) + * + * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s) + * @returns {Array} An array that contains nested array(s) made up of abbreviated building + sampling rate string(s) + */ +const extractBuildingPlusSamplingRate = function ( + buildingDataPointSamplingRateAbbrevArr +) { + // Array to store final result + const temperatureDifferenceOptionsAbbrevArr = []; + + // Calculate the index(es) that we wish to extract + const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions( + buildingDataPointSamplingRateAbbrevArr + ); + + foundIndexesArr.forEach((foundIndex) => { + // Extracted array for a single found index + const bldgDataPntSamplingRateAbbrvArr = + buildingDataPointSamplingRateAbbrevArr[foundIndex]; + + // Extract the building and sampling rate strings + // Note: we have ignored the second element + const [bldgAbbrv, , samplingRateAbbrv] = bldgDataPntSamplingRateAbbrvArr; + + // Create a new array that contains two elements, + // the building and sampling rate abbreviated strings + const bldgSamplingRateAbbrvArr = [bldgAbbrv, samplingRateAbbrv]; + + temperatureDifferenceOptionsAbbrevArr.push(bldgSamplingRateAbbrvArr); + }); + + return temperatureDifferenceOptionsAbbrevArr; +}; + +/** + * Determine if a chart requires raw observations instead of aggregated observations + * + * @param {String} selectedAggregationType The selected aggregation type + * @param {String} selectedAggregationDuration The selected aggregation duration + * @returns {Boolean} true if the chart requires raw observations, false if not + */ +const checkIfChartRequiresRawObservations = function ( + selectedAggregationType, + selectedAggregationDuration +) { + if ( + selectedAggregationType === "None (raw data)" && + selectedAggregationDuration === undefined + ) { + return true; + } else { + return false; + } +}; + +/** + * Get the abbreviated form of building IDs, phenomenon names and sensor sampling rates + * @param {String} buildingFullForm A string representation of the full form of a building ID + * @param {String} phenomenonFullForm A string representation of the full form of a phenomenon name + * @param {String} samplingRateFullForm A string representation of the full form of a sensor's sampling rate + * @returns {Array} An array of abbreviated strings + */ +const getBuildingSensorSamplingRateAbbreviation = function ( + buildingFullForm, + phenomenonFullForm, + samplingRateFullForm +) { + const fullFormToAbbreviationMapping = { + buildings: { + "Bau 101": "101", + "Bau 102": "102", + "Bau 107": "107", + "Bau 112": "112, 118", + "Bau 125": "125", + "Bau 225": "225", + "Other": "weather_station_521", + }, + + phenomenon: { + VL: "vl", + RL: "rl", + dT: "dT", + Durchfluss: "flow", + Leistung: "power", + Energie: "energy", + Energie_VERBR: "energy_verb", + Außentemp: "outside_temp", + }, + + samplingRate: { + "15 min": "15min", + "60 min": "60min", + }, + }; + + if ( + fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm] === undefined + ) + throw new Error( + "The provided building ID is not valid or is not supported by this function" + ); + + if ( + fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm] === + undefined + ) + throw new Error( + "The provided data point is not valid or is not supported by this function" + ); + + if ( + fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm] === + undefined + ) + throw new Error( + "The provided sampling rate is not valid or is not supported by this function" + ); + + const buildingAbbrev = + fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm]; + + const phenomenonAbbrev = + fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm]; + + const samplingRateAbbrev = + fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm]; + + return [buildingAbbrev, phenomenonAbbrev, samplingRateAbbrev]; +}; + +/** + * Get the abbreviated form for the currently selected options in ALL the drop down lists + * + * @param {Array} allSelectedOptionsArr An array containing four arrays, where each array contains the values of the selected options + * @returns {Array} An array which contains one or more nested arrays of abbreviations of building(s), data point(s) and sampling rate(s) + */ +const getAbbreviationsForSelectedOptionsFromAllDropDownLists = function ( + allSelectedOptionsArr +) { + // Note: The sampling rate array is the third array, therefore we skip one element + const [selectedBuildingDataPointOptionsSplitArr, , selectedSamplingRateArr] = + allSelectedOptionsArr; + + // The building is the first element + const selectedBuildingsArr = selectedBuildingDataPointOptionsSplitArr.map( + (selectedBuildingDataPoint) => selectedBuildingDataPoint[0] + ); + + // The data point is the second element + const selectedDataPointsArr = selectedBuildingDataPointOptionsSplitArr.map( + (selectedBuildingDataPoint) => selectedBuildingDataPoint[1] + ); + + // Assume that the buildings and data points arrays have equal length + // use one of the arrays for looping + if (selectedBuildingsArr.length !== selectedDataPointsArr.length) + throw new Error( + "The buildings array and data points array have different lengths" + ); + + return selectedBuildingsArr.map((selectedBuilding, i) => + getBuildingSensorSamplingRateAbbreviation( + selectedBuilding, + selectedDataPointsArr[i], + ...selectedSamplingRateArr + ) + ); +}; + +export { + splitMultipleOptionsTextDelimitedBySlash, + getSelectedOptionsFromAllDropDownLists, + checkIfSelectedOptionsContainTemperatureDifference, + deleteTemperatureDifferenceOptions, + extractTemperatureDifferenceOptions, + extractBuildingPlusSamplingRate, + checkIfChartRequiresRawObservations, + getAbbreviationsForSelectedOptionsFromAllDropDownLists, +}; diff --git a/public/js/src_modules/dropDownListProcessing.mjs b/public/js/src_modules/dropDownListProcessing.mjs new file mode 100644 index 0000000000000000000000000000000000000000..28d0eadcf154d53f4702cd5fcd389817267ccd08 --- /dev/null +++ b/public/js/src_modules/dropDownListProcessing.mjs @@ -0,0 +1,864 @@ +import { drawLineChartHighcharts } from "./chartLine.mjs"; + +import { + formatSensorThingsApiResponseForHeatMap, + drawHeatMapHighcharts, +} from "./chartHeatmap.mjs"; + +import { + formatSensorThingsApiResponseForScatterPlot, + drawScatterPlotHighcharts, +} from "./chartScatterPlot.mjs"; + +import { + formatAggregationResultForColumnChart, + drawColumnChartHighcharts, +} from "./chartColumn.mjs"; + +import { extractUniqueCalendarMonthsFromCalendarDates } from "./aggregateHelpers.mjs"; + +import { + calculateMinimumObservationValuesWithinInterval, + calculateMaximumObservationValuesWithinInterval, + calculateSumOfObservationValuesWithinInterval, + calculateAverageOfObservationValuesWithinInterval, +} from "./aggregate.mjs"; + +import { extractPropertiesFromFormattedDatastreamMetadata } from "./fetchedDataProcessing.mjs"; + +/** + * Draw a heatmap based on the selected options from a drop-down list + * + * @param {Array} selectedBuildingsDataPointsSamplingRateAbbrevArr An array which contains one or more nested arrays of abbreviations of building(s), data point(s) and sampling rate(s) + * @param {Array} observationsComboNestedArr An array that contains non-computed (raw) observations and computed (temperature difference, dT) observations + * @param {Object} extractedFormattedDatastreamProperties An object that contains array(s) of formatted Datastream properties + * @returns {undefined} undefined + */ +const drawHeatmapBasedOnSelectedOptions = function ( + selectedBuildingsDataPointsSamplingRateAbbrevArr, + observationsComboNestedArr, + extractedFormattedDatastreamProperties +) { + if (selectedBuildingsDataPointsSamplingRateAbbrevArr.length === 1) { + // Create formatted array(s) for observations + const formattedObservationsHeatMapNestedArr = + observationsComboNestedArr.map((observationsArr) => + formatSensorThingsApiResponseForHeatMap(observationsArr) + ); + + // Note: The resulting array is nested and is not suitable for heatmap, + // extract the nested array + const [formattedObservationsHeatMapArr] = + formattedObservationsHeatMapNestedArr; + + drawHeatMapHighcharts( + formattedObservationsHeatMapArr, + extractedFormattedDatastreamProperties + ); + } else if (selectedBuildingsDataPointsSamplingRateAbbrevArr.length < 1) { + throw new Error("Please select at least one data point"); + } else { + throw new Error( + "This type of chart (Heatmap) can only display one data point at a time" + ); + } +}; + +/** + * Draw a scatter plot based on the selected options from a drop-down list + * + * @param {Array} selectedBuildingsDataPointsSamplingRateAbbrevArr An array which contains one or more nested arrays of abbreviations of building(s), data point(s) and sampling rate(s) + * @param {Array} observationsComboNestedArr An array that contains non-computed (raw) observations and computed (temperature difference, dT) observations + * @param {Object} extractedFormattedDatastreamProperties An object that contains array(s) of formatted Datastream properties + * @returns {undefined} undefined + */ +const drawScatterPlotFromChartSelection = function ( + selectedBuildingsDataPointsSamplingRateAbbrevArr, + observationsComboNestedArr, + extractedFormattedDatastreamProperties +) { + // Check the length of buildings + data points + sampling rate array + if (selectedBuildingsDataPointsSamplingRateAbbrevArr.length >= 2) { + // Extract values for x-axis and y-axis + // x-axis values are first element of nested observations array + const [obsXAxisArr] = observationsComboNestedArr.slice(0, 1); + // y-axis values are rest of elements of nested observations array + const obsYAxisNestedArr = observationsComboNestedArr.slice(1); + + // Create formatted array(s) for observations + const formattedObservationsScatterPlotArr = obsYAxisNestedArr.map( + (obsYAxisArr) => + formatSensorThingsApiResponseForScatterPlot(obsXAxisArr, obsYAxisArr) + ); + + drawScatterPlotHighcharts( + formattedObservationsScatterPlotArr, + extractedFormattedDatastreamProperties + ); + } else { + throw new Error("A scatter plot chart requires at least two data points"); + } +}; + +/** + * Calculate the daily sum of observations and format these aggregated observations + * + * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s) + * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations + * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option + * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties + * @returns {Array} An array whose first element is the formatted aggregated (daily sum) observations. The second element is an object made up of extracted & formatted datastream properties + */ +const calculateAndFormatDailySumObservations = function ( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr +) { + // Calculate sum of values of observations - daily + const observationsSumDailyNestedArr = + calculateSumOfObservationValuesWithinInterval( + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarDatesNestedArr, + "daily" + ); + + // Format the observations - sum / daily + const formattedObservationsSumDailyNestedArr = + observationsSumDailyNestedArr.map((obsSumDailyArr, i) => + formatAggregationResultForColumnChart( + uniqueCalendarDatesNestedArr[i], + obsSumDailyArr + ) + ); + + // Extract the formatted metadata properties + const extractedFormattedDatastreamProperties = + extractPropertiesFromFormattedDatastreamMetadata( + formattedMetadataNestedArr, + true, + "daily", + "sum" + ); + + return [ + formattedObservationsSumDailyNestedArr, + extractedFormattedDatastreamProperties, + ]; +}; + +/** + * Calculate the monthly sum of observations and format these aggregated observations + * + * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s) + * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations + * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option + * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties + * @returns {Array} An array whose first element is the formatted aggregated (monthly sum) observations. The second element is an object made up of extracted & formatted datastream properties + */ +const calculateAndFormatMonthlySumObservations = function ( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr +) { + // Unique calendar months + const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map( + (uniqueCalendarDatesArr) => + extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr) + ); + + // Calculate sum of values of observations - monthly + const observationsSumMonthlyNestedArr = + calculateSumOfObservationValuesWithinInterval( + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarMonthsNestedArr, + "monthly" + ); + + // Format the observations - sum / monthly + const formattedObservationsSumMonthlyNestedArr = + observationsSumMonthlyNestedArr.map((obsSumMonthlyArr, i) => + formatAggregationResultForColumnChart( + uniqueCalendarMonthsNestedArr[i], + obsSumMonthlyArr + ) + ); + + // Extract the formatted metadata properties + const extractedFormattedDatastreamProperties = + extractPropertiesFromFormattedDatastreamMetadata( + formattedMetadataNestedArr, + true, + "monthly", + "sum" + ); + + return [ + formattedObservationsSumMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ]; +}; + +/** + * Calculate the daily maximum of observations and format these aggregated observations + * + * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s) + * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations + * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option + * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties + * @returns {Array} An array whose first element is the formatted aggregated (daily maximum) observations. The second element is an object made up of extracted & formatted datastream properties + */ +const calculateAndFormatDailyMaximumObservations = function ( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr +) { + // Calculate minimum of values of observations - daily + const observationsMaximumDailyNestedArr = + calculateMaximumObservationValuesWithinInterval( + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarDatesNestedArr, + "daily" + ); + + // Format the observations - min / daily + const formattedObservationsMaximumDailyNestedArr = + observationsMaximumDailyNestedArr.map((obsMinDailyArr, i) => + formatAggregationResultForColumnChart( + uniqueCalendarDatesNestedArr[i], + obsMinDailyArr + ) + ); + + // Extract the formatted metadata properties + const extractedFormattedDatastreamProperties = + extractPropertiesFromFormattedDatastreamMetadata( + formattedMetadataNestedArr, + true, + "daily", + "maximum" + ); + + return [ + formattedObservationsMaximumDailyNestedArr, + extractedFormattedDatastreamProperties, + ]; +}; + +/** + * Calculate the monthly maximum of observations and format these aggregated observations + * + * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s) + * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations + * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option + * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties + * @returns {Array} An array whose first element is the formatted aggregated (monthly maximum) observations. The second element is an object made up of extracted & formatted datastream properties + */ +const calculateAndFormatMonthlyMaximumObservations = function ( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr +) { + // Unique calendar months + const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map( + (uniqueCalendarDatesArr) => + extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr) + ); + + // Calculate minimum of values of observations - monthly + const observationsMaximumMonthlyNestedArr = + calculateMaximumObservationValuesWithinInterval( + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarMonthsNestedArr, + "monthly" + ); + + // Format the observations - max / monthly + const formattedObservationsMaximumMonthlyNestedArr = + observationsMaximumMonthlyNestedArr.map((obsMaxMonthlyArr, i) => + formatAggregationResultForColumnChart( + uniqueCalendarMonthsNestedArr[i], + obsMaxMonthlyArr + ) + ); + + // Extract the formatted metadata properties + const extractedFormattedDatastreamProperties = + extractPropertiesFromFormattedDatastreamMetadata( + formattedMetadataNestedArr, + true, + "monthly", + "maximum" + ); + + return [ + formattedObservationsMaximumMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ]; +}; + +/** + * Calculate the daily minimum of observations and format these aggregated observations + * + * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s) + * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations + * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option + * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties + * @returns {Array} An array whose first element is the formatted aggregated (daily minimum) observations. The second element is an object made up of extracted & formatted datastream properties + */ +const calculateAndFormatDailyMinimumObservations = function ( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr +) { + // Calculate minimum of values of observations - daily + const observationsMinimumDailyNestedArr = + calculateMinimumObservationValuesWithinInterval( + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarDatesNestedArr, + "daily" + ); + + // Format the observations - min / daily + const formattedObservationsMinimumDailyNestedArr = + observationsMinimumDailyNestedArr.map((obsMinDailyArr, i) => + formatAggregationResultForColumnChart( + uniqueCalendarDatesNestedArr[i], + obsMinDailyArr + ) + ); + + // Extract the formatted metadata properties + const extractedFormattedDatastreamProperties = + extractPropertiesFromFormattedDatastreamMetadata( + formattedMetadataNestedArr, + true, + "daily", + "minimum" + ); + + return [ + formattedObservationsMinimumDailyNestedArr, + extractedFormattedDatastreamProperties, + ]; +}; + +/** + * Calculate the monthly minimum of observations and format these aggregated observations + * + * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s) + * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations + * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option + * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties + * @returns {Array} An array whose first element is the formatted aggregated (monthly minimum) observations. The second element is an object made up of extracted & formatted datastream properties + */ +const calculateAndFormatMonthlyMinimumObservations = function ( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr +) { + // Unique calendar months + const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map( + (uniqueCalendarDatesArr) => + extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr) + ); + + // Calculate minimum of values of observations - monthly + const observationsMinimumMonthlyNestedArr = + calculateMinimumObservationValuesWithinInterval( + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarMonthsNestedArr, + "monthly" + ); + + // Format the observations - min / monthly + const formattedObservationsMinimumMonthlyNestedArr = + observationsMinimumMonthlyNestedArr.map((obsMinMonthlyArr, i) => + formatAggregationResultForColumnChart( + uniqueCalendarMonthsNestedArr[i], + obsMinMonthlyArr + ) + ); + + // Extract the formatted metadata properties + const extractedFormattedDatastreamProperties = + extractPropertiesFromFormattedDatastreamMetadata( + formattedMetadataNestedArr, + true, + "monthly", + "minimum" + ); + + return [ + formattedObservationsMinimumMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ]; +}; + +/** + * Calculate the daily average of observations and format these aggregated observations + * + * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s) + * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations + * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option + * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties + * @returns {Array} An array whose first element is the formatted aggregated (daily average) observations. The second element is an object made up of extracted & formatted datastream properties + */ +const calculateAndFormatDailyAverageObservations = function ( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr +) { + // Calculate average of values of observations - daily + const observationsAverageDailyNestedArr = + calculateAverageOfObservationValuesWithinInterval( + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarDatesNestedArr, + "daily" + ); + + // Format the observations - average / daily + const formattedObservationsAverageDailyNestedArr = + observationsAverageDailyNestedArr.map((obsAverageDailyArr, i) => + formatAggregationResultForColumnChart( + uniqueCalendarDatesNestedArr[i], + obsAverageDailyArr + ) + ); + + // Extract the formatted metadata properties + const extractedFormattedDatastreamProperties = + extractPropertiesFromFormattedDatastreamMetadata( + formattedMetadataNestedArr, + true, + "daily", + "average" + ); + + return [ + formattedObservationsAverageDailyNestedArr, + extractedFormattedDatastreamProperties, + ]; +}; + +/** + * Calculate the monthly average of observations and format these aggregated observations + * + * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s) + * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations + * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option + * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties + * @returns {Array} An array whose first element is the formatted aggregated (monthly average) observations. The second element is an object made up of extracted & formatted datastream properties + */ +const calculateAndFormatMonthlyAverageObservations = function ( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr +) { + // Unique calendar months + const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map( + (uniqueCalendarDatesArr) => + extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr) + ); + + // Calculate average of values of observations - monthly + const observationsAverageMonthlyNestedArr = + calculateAverageOfObservationValuesWithinInterval( + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarMonthsNestedArr, + "monthly" + ); + + // Format the observations - average / monthly + const formattedObservationsAverageMonthlyNestedArr = + observationsAverageMonthlyNestedArr.map((obsAverageMonthlyArr, i) => + formatAggregationResultForColumnChart( + uniqueCalendarMonthsNestedArr[i], + obsAverageMonthlyArr + ) + ); + + // Extract the formatted metadata properties + const extractedFormattedDatastreamProperties = + extractPropertiesFromFormattedDatastreamMetadata( + formattedMetadataNestedArr, + true, + "monthly", + "average" + ); + + return [ + formattedObservationsAverageMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ]; +}; + +/** + * Draw a line chart based on the selected aggregation options from a drop-down list + * + * @param {String} selectedAggregationType A string representing the selected aggregation type. The currently supported strings include `Sum`, `Maximum`, `Minimum` and `Average` + * @param {String} selectedAggregationDuration A string representing the selected aggregation duration. The currently supported strings include `Daily` and `Monthly` + * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations + * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option + * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s) + * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties + * @returns {undefined} undefined + */ +const drawLineChartBasedOnSelectedAggregationOptions = function ( + selectedAggregationType, + selectedAggregationDuration, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarDatesNestedArr, + formattedMetadataNestedArr +) { + if ( + selectedAggregationType === "Sum" && + selectedAggregationDuration === "Daily" + ) { + // Formatted observations and metadata for chart - daily / sum + const [ + formattedObservationsSumDailyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatDailySumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawLineChartHighcharts( + formattedObservationsSumDailyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationType === "Sum" && + selectedAggregationDuration === "Monthly" + ) { + // Formatted observations and metadata for chart - monthly / sum + const [ + formattedObservationsSumMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatMonthlySumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawLineChartHighcharts( + formattedObservationsSumMonthlyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationType === "Maximum" && + selectedAggregationDuration === "Daily" + ) { + // Formatted observations and metadata for chart - daily / maximum + const [ + formattedObservationsMaximumDailyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatDailyMaximumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawLineChartHighcharts( + formattedObservationsMaximumDailyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationType === "Maximum" && + selectedAggregationDuration === "Monthly" + ) { + // Formatted observations and metadata for chart - monthly / maximum + const [ + formattedObservationsMaximumMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatMonthlyMaximumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawLineChartHighcharts( + formattedObservationsMaximumMonthlyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationType === "Minimum" && + selectedAggregationDuration === "Daily" + ) { + // Formatted observations and metadata for chart - daily / minimum + const [ + formattedObservationsMinimumDailyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatDailyMinimumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawLineChartHighcharts( + formattedObservationsMinimumDailyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationType === "Minimum" && + selectedAggregationDuration === "Monthly" + ) { + // Formatted observations and metadata for chart - monthly / minimum + const [ + formattedObservationsMinimumMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatMonthlyMinimumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawLineChartHighcharts( + formattedObservationsMinimumMonthlyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationType === "Average" && + selectedAggregationDuration === "Daily" + ) { + // Formatted observations and metadata for chart - daily / average + const [ + formattedObservationsAverageDailyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatDailyAverageObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawLineChartHighcharts( + formattedObservationsAverageDailyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationType === "Average" && + selectedAggregationDuration === "Monthly" + ) { + // Formatted observations and metadata for chart - monthly / average + const [ + formattedObservationsAverageMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatMonthlyAverageObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawLineChartHighcharts( + formattedObservationsAverageMonthlyNestedArr, + extractedFormattedDatastreamProperties + ); + } +}; + +/** + * Draw a column chart based on the selected aggregation options from a drop-down list + * + * @param {String} selectedAggregationType A string representing the selected aggregation type. The currently supported strings include `Sum`, `Maximum`, `Minimum` and `Average` + * @param {String} selectedAggregationDuration A string representing the selected aggregation duration. The currently supported strings include `Daily` and `Monthly` + * @param {Array} observationsAggregationNestedArr An array made up of sub-array(s) of aggregated observations + * @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option + * @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s) + * @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties + * @returns {undefined} undefined + */ +const drawColumnChartBasedOnSelectedAggregationOptions = function ( + selectedAggregationTypeArr, + selectedAggregationDuration, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + uniqueCalendarDatesNestedArr, + formattedMetadataNestedArr +) { + if ( + selectedAggregationTypeArr === "Sum" && + selectedAggregationDuration === "Daily" + ) { + // Formatted observations and metadata for chart - daily / sum + const [ + formattedObservationsSumDailyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatDailySumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawColumnChartHighcharts( + formattedObservationsSumDailyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationTypeArr === "Sum" && + selectedAggregationDuration === "Monthly" + ) { + // Formatted observations and metadata for chart - monthly / sum + const [ + formattedObservationsSumMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatMonthlySumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawColumnChartHighcharts( + formattedObservationsSumMonthlyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationTypeArr === "Maximum" && + selectedAggregationDuration === "Daily" + ) { + // Formatted observations and metadata for chart - daily / maximum + const [ + formattedObservationsMaximumDailyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatDailyMaximumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawColumnChartHighcharts( + formattedObservationsMaximumDailyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationTypeArr === "Maximum" && + selectedAggregationDuration === "Monthly" + ) { + // Formatted observations and metadata for chart - monthly / maximum + const [ + formattedObservationsMaximumMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatMonthlyMaximumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawColumnChartHighcharts( + formattedObservationsMaximumMonthlyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationTypeArr === "Minimum" && + selectedAggregationDuration === "Daily" + ) { + // Formatted observations and metadata for chart - daily / minimum + const [ + formattedObservationsMinimumDailyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatDailyMinimumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawColumnChartHighcharts( + formattedObservationsMinimumDailyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationTypeArr === "Minimum" && + selectedAggregationDuration === "Monthly" + ) { + // Formatted observations and metadata for chart - monthly / minimum + const [ + formattedObservationsMinimumMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatMonthlyMinimumObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawColumnChartHighcharts( + formattedObservationsMinimumMonthlyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationTypeArr === "Average" && + selectedAggregationDuration === "Daily" + ) { + // Formatted observations and metadata for chart - daily / average + const [ + formattedObservationsAverageDailyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatDailyAverageObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawColumnChartHighcharts( + formattedObservationsAverageDailyNestedArr, + extractedFormattedDatastreamProperties + ); + } else if ( + selectedAggregationTypeArr === "Average" && + selectedAggregationDuration === "Monthly" + ) { + // Formatted observations and metadata for chart - monthly / average + const [ + formattedObservationsAverageMonthlyNestedArr, + extractedFormattedDatastreamProperties, + ] = calculateAndFormatMonthlyAverageObservations( + uniqueCalendarDatesNestedArr, + observationsAggregationNestedArr, + selectedSamplingRateAbbrev, + formattedMetadataNestedArr + ); + + drawColumnChartHighcharts( + formattedObservationsAverageMonthlyNestedArr, + extractedFormattedDatastreamProperties + ); + } +}; + +export { + drawHeatmapBasedOnSelectedOptions, + drawScatterPlotFromChartSelection, + drawLineChartBasedOnSelectedAggregationOptions, + drawColumnChartBasedOnSelectedAggregationOptions, +};