diff --git a/public/js/aggregate.js b/public/js/aggregate.js index 5157411fcb364409b7cfce5908cd4a0f88cba29b..53148985a0498409e00b3d80e0b2f7f009b74d41 100644 --- a/public/js/aggregate.js +++ b/public/js/aggregate.js @@ -3,10 +3,7 @@ import { BASE_URL, QUERY_PARAMS_COMBINED, - getDatastreamIdFromBuildingNumber, - createObservationsUrl, - performGetRequestUsingAxios, - extractCombinedObservationsFromAllPages, + getMetadataPlusObservationsFromSingleOrMultipleDatastreams, } from "./appChart.js"; /** @@ -135,28 +132,23 @@ const aggregateObservationsWithinTimeInterval = function ( * Test aggregation of observations from a single datastream */ const testAggregation = async function () { - // Datastream ID - const datastreamIdBau225VL = getDatastreamIdFromBuildingNumber( - "225", - "vl", - "60min" - ); + const sensorOfInterestNestedArr = [["225", "vl", "60min"]]; - // Observations URL - const observationsUrlBau225VL = createObservationsUrl( - BASE_URL, - datastreamIdBau225VL - ); + const observationsPlusMetadata = + await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( + BASE_URL, + QUERY_PARAMS_COMBINED, + sensorOfInterestNestedArr + ); - // Observations array - const observationsBau225VL = await extractCombinedObservationsFromAllPages( - performGetRequestUsingAxios(observationsUrlBau225VL, QUERY_PARAMS_COMBINED) - ); + // Extract the observations and metadata for each sensor + // Array elements in same order as input array + const [[obsSensorOneArr], [metadataSensorOne]] = observationsPlusMetadata; // Aggregated observations const observationsBau225VLAggregated = aggregateObservationsWithinTimeInterval( - observationsBau225VL, + obsSensorOneArr, "60 min", "2020-02-01", "2020-03-31" diff --git a/public/js/appChart.js b/public/js/appChart.js index 3b3a6754ed79b6f6ddfbd813dd37d85ceea77066..181efd4e0907057aa476b5dd8b25aa49291fb15a 100644 --- a/public/js/appChart.js +++ b/public/js/appChart.js @@ -91,11 +91,9 @@ const getDatastreamIdFromBuildingNumber = function ( ) return; - const datastreamIdMatched = Number( + return Number( buildingToDatastreamMapping[buildingNumber][phenomenon][samplingRate] ); - - return datastreamIdMatched; }; /** @@ -106,8 +104,7 @@ const getDatastreamIdFromBuildingNumber = function ( */ const createDatastreamUrl = function (baseUrl, datastreamID) { if (!datastreamID) return; - const fullDatastreamURL = `${baseUrl}/Datastreams(${datastreamID})`; - return fullDatastreamURL; + return `${baseUrl}/Datastreams(${datastreamID})`; }; /** @@ -118,8 +115,7 @@ const createDatastreamUrl = function (baseUrl, datastreamID) { */ const createObservationsUrl = function (baseUrl, datastreamID) { if (!datastreamID) return; - const fullObservationsURL = `${baseUrl}/Datastreams(${datastreamID})/Observations`; - return fullObservationsURL; + return `${baseUrl}/Datastreams(${datastreamID})/Observations`; }; /** @@ -130,23 +126,33 @@ const createObservationsUrl = function (baseUrl, datastreamID) { */ const createTemporalFilterString = function (dateStart, dateStop) { if (!dateStart || !dateStop) return; - const filterString = `resultTime ge ${dateStart}T00:00:00.000Z and resultTime le ${dateStop}T00:00:00.000Z`; - return filterString; + return `resultTime ge ${dateStart}T00:00:00.000Z and resultTime le ${dateStop}T00:00:00.000Z`; +}; + +/** + * Create a query parameter object that should be sent together with a HTTP GET request using the Axios library + * @param {String} dateStart Start date (for temporal filter) in YYYY-MM-DD format + * @param {String} dateStop Stop date (for temporal filter) in YYYY-MM-DD format + * @returns {Object} A query parameter object + */ +const createUrlParametersForGetRequest = function (dateStart, dateStop) { + const QUERY_PARAM_RESULT_FORMAT = "dataArray"; + const QUERY_PARAM_ORDER_BY = "phenomenonTime asc"; + const QUERY_PARAM_FILTER = createTemporalFilterString(dateStart, dateStop); + const QUERY_PARAM_SELECT = "result,phenomenonTime"; + + return { + "$resultFormat": QUERY_PARAM_RESULT_FORMAT, + "$orderBy": QUERY_PARAM_ORDER_BY, + "$filter": QUERY_PARAM_FILTER, + "$select": QUERY_PARAM_SELECT, + }; }; -const QUERY_PARAM_RESULT_FORMAT = "dataArray"; -const QUERY_PARAM_ORDER_BY = "phenomenonTime asc"; -const QUERY_PARAM_FILTER = createTemporalFilterString( +const QUERY_PARAMS_COMBINED = createUrlParametersForGetRequest( "2020-01-01", "2021-01-01" ); -const QUERY_PARAM_SELECT = "result,phenomenonTime"; -const QUERY_PARAMS_COMBINED = { - "$resultFormat": QUERY_PARAM_RESULT_FORMAT, - "$orderBy": QUERY_PARAM_ORDER_BY, - "$filter": QUERY_PARAM_FILTER, - "$select": QUERY_PARAM_SELECT, -}; /** * Perform a GET request using the Axios library @@ -446,22 +452,113 @@ const formatSensorThingsApiResponseForLineChart = function (obsArray) { return dataSTAFormatted; }; +/** + * Extract the properties that make up the formatted datastream metadata object(s) + * @param {Array} formattedDatastreamsMetadataArr An array of formatted metadata object(s) from one or more datastreams + * @returns {Object} An object that contains array(s) of formatted datastream metadata properties + */ +const extractPropertiesFromDatastreamMetadata = function ( + formattedDatastreamsMetadataArr +) { + // Create arrays from the properties of the formatted datastream metadata + const datastreamDescriptionsArr = formattedDatastreamsMetadataArr.map( + (datastreamMetadata) => datastreamMetadata.datastreamDescription + ); + + const datastreamNamesArr = formattedDatastreamsMetadataArr.map( + (datastreamMetadata) => datastreamMetadata.datastreamName + ); + + const phenomenonNamesArr = formattedDatastreamsMetadataArr.map( + (datastreamMetadata) => datastreamMetadata.phenomenonName + ); + + const unitOfMeasurementSymbolsArr = formattedDatastreamsMetadataArr.map( + (datastreamMetadata) => datastreamMetadata.unitOfMeasurementSymbol + ); + + return { + datastreamDescriptionsArr, + datastreamNamesArr, + phenomenonNamesArr, + unitOfMeasurementSymbolsArr, + }; +}; + +/** + * Extracts the sampling rate substring from a datastream name string + * @param {Array} datastreamNamesArr An array of datastream name(s) + * @returns {Array} An array containing the sampling rate substring(s) + */ +const extractSamplingRateFromDatastreamName = function (datastreamNamesArr) { + // The sampling rate string is the last word in the Datastream name string + return datastreamNamesArr.map((datastreamName) => + datastreamName.split(" ").pop() + ); +}; + +/** + * Concatenates metadata properties to create a string for either the title or subtitle of a line chart + * @param {Array} datastreamMetadataPropArr An array of metadata property strings + * @returns {String} A string of comma separated metadata property strings + */ +const createCombinedTextForLineChartTitles = function ( + datastreamMetadataPropArr +) { + return datastreamMetadataPropArr.join(", "); +}; + +/** + * Creates an options object for each series drawn in the line chart + * @param {Array} formattedObsArraysForLineChart An array of formatted observation array(s) from one or more datastreams + * @param {Array} phenomenonNamesArr An array of phenomenon name(s) + * @param {Array} phenomenonSymbolsArr An array of phenomenon symbol(s) + * @returns {Array} An array made up of series options object(s) + */ +const createSeriesOptionsForLineChart = function ( + formattedObsArraysForLineChart, + phenomenonNamesArr, + phenomenonSymbolsArr +) { + // An array of colors provided by the Highcharts object + const seriesColors = Highcharts.getOptions().colors; + + // Create an array of seriesOptions objects + // Assumes that the observation array of arrays, phenomenon names array and phenomenon symbols array are of equal length + // Use one of the arrays for looping + return formattedObsArraysForLineChart.map((formattedObsArray, i) => { + return { + name: `${phenomenonNamesArr[i]} (${phenomenonSymbolsArr[i]})`, + data: formattedObsArray, + color: seriesColors[i], + turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release + }; + }); +}; + /** * Draw a line chart using Highcharts library - * @param {Array} formattedObsArrayForLineChart Response from SensorThings API formatted for use in a line chart - * @param {Object} formattedDatastreamMetadata Object containing Datastream metadata + * @param {Array} formattedObsArraysForLineChart An array made up of formatted observation array(s) suitable for use in a line chart + * @param {Object} formattedDatastreamMetadataArr An array made up of object(s) containing Datastream metadata * @returns {undefined} undefined */ const drawLineChartHighcharts = function ( - formattedObsArrayForLineChart, - formattedDatastreamMetadata + formattedObsArraysForLineChart, + formattedDatastreamMetadataArr ) { + // Arrays of datastream properties const { - datastreamDescription: DATASTREAM_DESCRIPTION, - datastreamName: DATASTREAM_NAME, - phenomenonName: PHENOMENON_NAME, - unitOfMeasurementSymbol: PHENOMENON_SYMBOL, - } = formattedDatastreamMetadata; + datastreamNamesArr, + phenomenonNamesArr, + unitOfMeasurementSymbolsArr, + } = extractPropertiesFromDatastreamMetadata(formattedDatastreamMetadataArr); + + // Create the array of series options object(s) + const seriesOptionsArr = createSeriesOptionsForLineChart( + formattedObsArraysForLineChart, + phenomenonNamesArr, + unitOfMeasurementSymbolsArr + ); Highcharts.stockChart("chart-line", { chart: { @@ -473,25 +570,24 @@ const drawLineChartHighcharts = function ( }, title: { - text: DATASTREAM_DESCRIPTION, + text: createCombinedTextForLineChartTitles(phenomenonNamesArr), "align": "left", }, subtitle: { - text: DATASTREAM_NAME, + text: `Sampling rate(s): ${createCombinedTextForLineChartTitles( + extractSamplingRateFromDatastreamName(datastreamNamesArr) + )}`, align: "left", }, - series: [ - { - name: `${PHENOMENON_NAME} (${PHENOMENON_SYMBOL})`, - data: formattedObsArrayForLineChart, - tooltip: { - valueDecimals: 2, - }, - turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release - }, - ], + tooltip: { + pointFormat: + '{series.name}: {point.y}
', + valueDecimals: 2, + }, + + series: seriesOptionsArr, }); }; @@ -670,7 +766,7 @@ const checkForAndDeleteUniqueObservationsFromLargerArray = function ( * Extracts and combines observation values from two input observation arrays of equal length * @param {Array} obsArrayOne First set of N observations (timestamp + value) * @param {Array} obsArrayTwo Second set of N observations (timestamp + value) - * @returns {Array} A 2*N array of observation values from both input observation arrays + * @returns {Array} A N*2 array of observation values from both input observation arrays */ const createCombinedObservationValues = function (obsArrayOne, obsArrayTwo) { // Extract the values from the two observation arrays @@ -712,9 +808,9 @@ const formatSensorThingsApiResponseForScatterPlot = function ( /** * Draw a scatter plot using Highcharts library - * @param {*} formattedObsArrayForSeriesOnePlusSeriesTwo Response from SensorThings API formatted for use in a scatter plot - * @param {*} formattedDatastreamMetadataSeriesOne Object containing Datastream metadata for the first chart series - * @param {*} formattedDatastreamMetadataSeriesTwo Object containing Datastream metadata for the second chart series + * @param {Array} formattedObsArrayForSeriesOnePlusSeriesTwo Response from SensorThings API formatted for use in a scatter plot + * @param {Object} formattedDatastreamMetadataSeriesOne Object containing Datastream metadata for the first chart series + * @param {Object} formattedDatastreamMetadataSeriesTwo Object containing Datastream metadata for the second chart series * @returns {undefined} */ const drawScatterPlotHighcharts = function ( @@ -849,10 +945,15 @@ const combineResultsFromAllPages = async function (httpGetRequestPromise) { if (!httpGetRequestPromise) return; const lastSuccess = await httpGetRequestPromise; + + // The "success" objects contain a "data" object which in turn has a "value" property + // If the "data" object in turn has a "@iot.nextLink" property, then a next page exists if (lastSuccess.data["@iot.nextLink"]) { const nextLinkSuccess = await combineResultsFromAllPages( axios.get(lastSuccess.data["@iot.nextLink"]) ); + // The "data" object in turn has a "value" property + // The "value" property's value is an array nextLinkSuccess.data.value = lastSuccess.data.value.concat( nextLinkSuccess.data.value ); @@ -902,32 +1003,6 @@ const extractCombinedObservationsFromAllPages = async function ( } }; -/** - * Retrieve the metadata for a Datastream as well as the Observations corresponding to it - * @async - * @param {Promise} metadataPlusObsPromiseArray An array that contains two promises, one for datastream metadata, the other for observations - * @returns {Promise} A promise that contains two arrays when fulfilled, one for datastream metadata and the other for observations - */ -const getMetadataPlusObservationsFromSingleDatastream = async function ( - metadataPlusObsPromiseArray -) { - try { - // Array to store our final result - const combinedResolvedPromises = []; - - // Use for/of loop - we need to maintain the order of execution of the async operations - for (const promise of metadataPlusObsPromiseArray) { - // Resolved value of a single promise - const resolvedPromise = await promise; - combinedResolvedPromises.push(resolvedPromise); - } - - return combinedResolvedPromises; - } catch (err) { - console.error(err); - } -}; - /** * Retrieve all the Observations from an array of Observations promises * @async @@ -955,55 +1030,55 @@ const getObservationsFromMultipleDatastreams = async function ( }; /** - * Retrieve the metadata from multiple Datastreams and the Observations corresponding to these Datastreams - * @async - * @param {Array} bldgSensorSamplingRateArr A 3*N array containing buildings, sensors & sampling rates as strings, e.g. ["101", "rl", "60min"] - * @returns {Promise} A promise that contains a N*2 array (the first element is an array of Observations and the second element is an array of Datastream metadata objects) when fulfilled + * Retrieve the metadata from a single Datastream or multiple Datastreams and the Observations corresponding to the Datastream(s) + * @param {String} baseUrl Base URL of the STA server + * @param {Object} urlParamObj The URL parameters to be sent together with the GET request + * @param {Array} bldgSensorSamplingRateArr A N*1 array (where N >= 1) containing a nested array of buildings, sensors & sampling rates as strings, i.e. [["101", "rl", "15min"]] or [["101", "rl", "15min"], ["102", "vl", "60min"]] or [["101", "rl", "15min"], ["102", "vl", "60min"], ["225", "vl", "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 */ -const getMetadataPlusObservationsFromMultipleDatastreams = async function ( - bldgSensorSamplingRateArr -) { - try { - if (!bldgSensorSamplingRateArr) return; - - // Datastreams IDs - const datastreamsIdsArr = bldgSensorSamplingRateArr.map( - (bldgSensorSamplingRate) => - getDatastreamIdFromBuildingNumber(...bldgSensorSamplingRate) - ); +const getMetadataPlusObservationsFromSingleOrMultipleDatastreams = + async function (baseUrl, urlParamObj, bldgSensorSamplingRateArr) { + try { + if (!bldgSensorSamplingRateArr) return; + + // Datastreams IDs + const datastreamsIdsArr = bldgSensorSamplingRateArr.map( + (bldgSensorSamplingRate) => + getDatastreamIdFromBuildingNumber(...bldgSensorSamplingRate) + ); - // Observations URLs - const observationsUrlArr = datastreamsIdsArr.map((datastreamId) => - createObservationsUrl(BASE_URL, datastreamId) - ); + // Observations URLs + const observationsUrlArr = datastreamsIdsArr.map((datastreamId) => + createObservationsUrl(baseUrl, datastreamId) + ); - // Datastreams URLs - const datastreamsUrlArr = datastreamsIdsArr.map((datastreamId) => - createDatastreamUrl(BASE_URL, datastreamId) - ); + // Datastreams URLs + const datastreamsUrlArr = datastreamsIdsArr.map((datastreamId) => + createDatastreamUrl(baseUrl, datastreamId) + ); - // Promise objects - Observations - const observationsPromisesArr = observationsUrlArr.map((obsUrl) => - extractCombinedObservationsFromAllPages( - performGetRequestUsingAxios(obsUrl, QUERY_PARAMS_COMBINED) - ) - ); + // Promise objects - Observations + const observationsPromisesArr = observationsUrlArr.map((obsUrl) => + extractCombinedObservationsFromAllPages( + performGetRequestUsingAxios(obsUrl, urlParamObj) + ) + ); - // Observations array - const observationsArr = await getObservationsFromMultipleDatastreams( - observationsPromisesArr - ); + // Observations array + const observationsArr = await getObservationsFromMultipleDatastreams( + observationsPromisesArr + ); - // Metadata array - const metadataArr = await getMetadataFromMultipleDatastreams( - datastreamsUrlArr - ); + // Metadata array + const metadataArr = await getMetadataFromMultipleDatastreams( + datastreamsUrlArr + ); - return [observationsArr, metadataArr]; - } catch (err) { - console.error(err); - } -}; + return [observationsArr, metadataArr]; + } catch (err) { + console.error(err); + } + }; /** * Calculates the temperature difference, dT, between Vorlauf temperature [VL] and Rücklauf temperature [RL] (i.e., dT = VL - RL) @@ -1026,7 +1101,9 @@ const calculateVorlaufMinusRuecklaufTemperature = async function ( const SAMPLING_RATE = samplingRate; const observationsPlusMetadata = - await getMetadataPlusObservationsFromMultipleDatastreams( + await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( + BASE_URL, + QUERY_PARAMS_COMBINED, bldgSensorSamplingRateArr ); @@ -1116,46 +1193,75 @@ const drawScatterPlotHCTest2 = async function () { ]; const observationsPlusMetadata = - await getMetadataPlusObservationsFromMultipleDatastreams( + await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( + BASE_URL, + QUERY_PARAMS_COMBINED, sensorsOfInterestArr ); - // Extract the observations and metadata for each sensor - // Array elements in same order as input array - const [ - [obsSensorOneArr, obsSensorTwoArr], - [metadataSensorOne, metadataSensorTwo], - ] = observationsPlusMetadata; + // Extract the combined arrays for observations and metadata + const [observationsArr, metadataArr] = observationsPlusMetadata; + + // Create formatted array(s) for observations + // This function expects two arguments, these are unpacked using the spread operator + const formattedObsScatterPlotArr = + formatSensorThingsApiResponseForScatterPlot(...observationsArr); + + // Create formatted array(s) for metadata + const formattedMetadataArr = metadataArr.map((metadata) => + formatDatastreamMetadataForChart(metadata) + ); + // This function expects three arguments, the second and third are unpacked using the spread operator drawScatterPlotHighcharts( - formatSensorThingsApiResponseForScatterPlot( - obsSensorOneArr, - obsSensorTwoArr - ), - formatDatastreamMetadataForChart(metadataSensorOne), - formatDatastreamMetadataForChart(metadataSensorTwo) + formattedObsScatterPlotArr, + ...formattedMetadataArr + ); +}; + +/** + * Test drawing of line chart with multiple series + */ +const testLineChartMultipleSeries = async function () { + const sensorsOfInterestArr = [ + ["225", "vl", "60min"], + ["125", "rl", "60min"], + ["weather_station_521", "outside_temp", "60min"], + ]; + + const observationsPlusMetadata = + await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( + BASE_URL, + QUERY_PARAMS_COMBINED, + sensorsOfInterestArr + ); + + // Extract the observations and metadata arrays + const [observationsArr, metadataArr] = observationsPlusMetadata; + + // Format the observations and metadata + const formattedObservationsArr = observationsArr.map((observations) => + formatSensorThingsApiResponseForLineChart(observations) ); + + const formattedMetadataArr = metadataArr.map((metadata) => + formatDatastreamMetadataForChart(metadata) + ); + + drawLineChartHighcharts(formattedObservationsArr, formattedMetadataArr); }; -(async () => { - // await drawScatterPlotHCTest2(); - await drawHeatmapHCUsingTempDifference(); -})(); +// drawScatterPlotHCTest2(); +// drawHeatmapHCUsingTempDifference(); +// testLineChartMultipleSeries() export { BASE_URL, QUERY_PARAMS_COMBINED, - getDatastreamIdFromBuildingNumber, - createDatastreamUrl, - createObservationsUrl, - createTemporalFilterString, - performGetRequestUsingAxios, - getMetadataFromSingleDatastream, formatDatastreamMetadataForChart, formatSensorThingsApiResponseForHeatMap, drawHeatMapHighcharts, formatSensorThingsApiResponseForLineChart, drawLineChartHighcharts, - extractCombinedObservationsFromAllPages, - getMetadataPlusObservationsFromSingleDatastream, + getMetadataPlusObservationsFromSingleOrMultipleDatastreams, }; diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js index e069f2b8b21bd1278a0211ba206afcc2bcd69398..44428a511095493510a9bfb001784744c98126d1 100644 --- a/public/js/dropDownList.js +++ b/public/js/dropDownList.js @@ -3,18 +3,12 @@ import { BASE_URL, QUERY_PARAMS_COMBINED, - getDatastreamIdFromBuildingNumber, - createDatastreamUrl, - createObservationsUrl, - performGetRequestUsingAxios, - getMetadataFromSingleDatastream, formatDatastreamMetadataForChart, formatSensorThingsApiResponseForHeatMap, drawHeatMapHighcharts, formatSensorThingsApiResponseForLineChart, drawLineChartHighcharts, - extractCombinedObservationsFromAllPages, - getMetadataPlusObservationsFromSingleDatastream, + getMetadataPlusObservationsFromSingleOrMultipleDatastreams, } from "./appChart.js"; const buildingsAvailableSensorsArr = [ @@ -268,13 +262,8 @@ const selectChartTypeFromDropDown = async function () { if (selectedOptions === undefined) return; - const abbreviationsArr = getBuildingSensorSamplingRateAbbreviation( - ...selectedOptions - ); - - const selectedDatastream = getDatastreamIdFromBuildingNumber( - ...abbreviationsArr - ); + const selectedOptionsAbbreviationsArr = + getBuildingSensorSamplingRateAbbreviation(...selectedOptions); const selectedChartType = document.querySelector( "#drop-down--chart-type" @@ -285,40 +274,39 @@ const selectChartTypeFromDropDown = async function () { // Display the loading indicator showLoadingSpinner(); - const URL_DATASTREAM = createDatastreamUrl(BASE_URL, selectedDatastream); - const URL_OBSERVATIONS = createObservationsUrl( - BASE_URL, - selectedDatastream - ); + // The `getMetadataPlusObservationsFromSingleOrMultipleDatastreams` function expects a nested array structure + const abbreviationsNestedArr = [selectedOptionsAbbreviationsArr]; + + const observationsPlusMetadata = + await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( + BASE_URL, + QUERY_PARAMS_COMBINED, + abbreviationsNestedArr + ); - // Create promises - const promiseDatastreamMetadata = - getMetadataFromSingleDatastream(URL_DATASTREAM); - const promiseCombinedObservations = extractCombinedObservationsFromAllPages( - performGetRequestUsingAxios(URL_OBSERVATIONS, QUERY_PARAMS_COMBINED) + // Extract the combined arrays for observations and metadata + const [observationsArr, metadataArr] = observationsPlusMetadata; + + // Create formatted array(s) for observations - line chart + const formattedObsLineChartArr = observationsArr.map((observations) => + formatSensorThingsApiResponseForLineChart(observations) ); - // Pass promises to our async function - const metadataPlusObservations = - await getMetadataPlusObservationsFromSingleDatastream([ - promiseCombinedObservations, - promiseDatastreamMetadata, - ]); + // Create formatted array(s) for observations - heatmap + const formattedObsHeatMapArr = observationsArr.map((observations) => + formatSensorThingsApiResponseForHeatMap(observations) + ); - // Extract the metadata and the observations from resulting array - const combinedObs = metadataPlusObservations[0]; - const datastreamMetadata = metadataPlusObservations[1]; + // Create formatted array(s) for metadata - same for both chart types + const formattedMetadataArr = metadataArr.map((metadata) => + formatDatastreamMetadataForChart(metadata) + ); if (selectedChartType === "Line") { - drawLineChartHighcharts( - formatSensorThingsApiResponseForLineChart(combinedObs), - formatDatastreamMetadataForChart(datastreamMetadata) - ); + drawLineChartHighcharts(formattedObsLineChartArr, formattedMetadataArr); } else if (selectedChartType === "Heatmap") { - drawHeatMapHighcharts( - formatSensorThingsApiResponseForHeatMap(combinedObs), - formatDatastreamMetadataForChart(datastreamMetadata) - ); + // First need to extract the nested arrays for the formatted observations and metadata + drawHeatMapHighcharts(...formattedObsHeatMapArr, ...formattedMetadataArr); } } catch (err) { console.error(err);