diff --git a/public/js/appChart.js b/public/js/appChart.js index 2cb42c72ae856cd3fd4ed1ccd5fdf697d1e4652c..c66a8b8f99a9f641f2b47ad44789c68f86799517 100644 --- a/public/js/appChart.js +++ b/public/js/appChart.js @@ -78,6 +78,10 @@ const getDatastreamIdFromBuildingNumber = function ( energy: { "15min": "122", "60min": "128" }, energy_verb: { "15min": "134", "60min": "140" }, }, + + weather_station_521: { + outside_temp: { "15min": "141", "60min": "142" }, + }, }; if ( @@ -163,7 +167,7 @@ const axiosGetRequest = function (urlObservations, urlParamObj) { * @param {String} urlDatastream A URL that fetches a Datastream from an STA instance * @returns {Promise} A promise that contains a metadata object for a Datastream when fulfilled */ -const getDatastreamMetadata = async function (urlDatastream) { +const getMetadataFromSingleDatastream = async function (urlDatastream) { try { // Extract properties of interest const { @@ -176,6 +180,32 @@ const getDatastreamMetadata = async function (urlDatastream) { } }; +/** + * Retrieve metadata from multiple datastreams + * @async + * @param {Array} datastreamsUrlArr An array that contains N Datastream URL strings + * @returns {Promise} A promise that contains an array of N Datastream metadata objects when fulfilled + */ +const getMetadataFromMultipleDatastreams = async function (datastreamsUrlArr) { + try { + // Array to store our final result + const datastreamMetadataArr = []; + + // Use for/of loop - we need to maintain the order of execution of the async operations + for (const datastreamUrl of datastreamsUrlArr) { + // Metadata from a single Datastream + const datastreamMetadata = await getMetadataFromSingleDatastream( + datastreamUrl + ); + datastreamMetadataArr.push(datastreamMetadata); + } + + return datastreamMetadataArr; + } catch (err) { + console.error(err); + } +}; + /** * Format the response containing a Datastream's metadata from Sensorthings API * @param {Object} datastreamMetadata An object containing a Datastream's metadata @@ -371,9 +401,9 @@ const drawHeatMapHC = function ( /** * Convert the observations' phenomenonTime from an ISO 8601 string to Unix epoch * @param {Array} obsArray Response from SensorThings API as array - * @returns {Array} Array of formatted observations suitable for use in a line chart + * @returns {Array} Array of formatted observations suitable for use in a line chart or scatter plot */ -const formatSTAResponseForLineChart = function (obsArray) { +const formatSTAResponseForLineChartOrScatterPlot = function (obsArray) { if (!obsArray) return; const dataSTAFormatted = obsArray.map((result) => { @@ -434,6 +464,273 @@ const drawLineChartHC = function ( }); }; +/** + * Determines the timestamps that are missing from a smaller set of observations. Based on the comparison of two observation arrays, where one array is larger than the other + * @param {Array} obsTimestampArrayOne An array of timestamps for the first set of observations + * @param {Array} obsTimestampArrayTwo An array of timstamps for the second set of observations + * @returns {Array} An array of timestamps missing from either set of observations + */ +const getSymmetricDifferenceBetweenArrays = function ( + obsTimestampArrayOne, + obsTimestampArrayTwo +) { + const differenceBetweenArrays = obsTimestampArrayOne + .filter((timestampOne) => !obsTimestampArrayTwo.includes(timestampOne)) + .concat( + obsTimestampArrayTwo.filter( + (timestampTwo) => !obsTimestampArrayTwo.includes(timestampTwo) + ) + ); + + return differenceBetweenArrays; +}; + +/** + * Determines the indexes of timestamps that are missing from a smaller set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other + * @param {Array} missingTimestampsArr An array of strings representing the missing timestamps + * @param {Array} largerObsTimestampArr An array of timestamps for the larger array of observations + * @returns {Array} An array of the indexes of the missing observations + */ +const getIndexOfMissingObservation = function ( + missingTimestampsArr, + largerObsTimestampArr +) { + const indexesMissingObs = missingTimestampsArr.map((index) => + largerObsTimestampArr.indexOf(index) + ); + + return indexesMissingObs; +}; + +/** + * Removes observations (by modifying array in place) from a larger set of observations that are missing from a smaller set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other + * @param {Array} missingIndexesArr An array of the indexes of the observations missing from the smaller set of observations + * @param {Array} largerObsArr The larger array of observations (timestamp + value) which is modified in place + * @returns {undefined} + */ +const removeMissingObservationFromLargerArray = function ( + missingIndexesArr, + largerObsArr +) { + missingIndexesArr.forEach((index) => { + if (index > -1) { + largerObsArr.splice(index, 1); + } + }); +}; + +/** + * Compares the length of two input arrays to determine the larger one + * @param {Array} firstArr First input array + * @param {Array} secondArr Second input array + * @returns {Array} The larger array + */ +const getLargerArrayBetweenTwoInputArrays = function (firstArr, secondArr) { + if (firstArr.length === secondArr.length) return; + + if (firstArr.length > secondArr.length) return firstArr; + + if (firstArr.length < secondArr.length) return secondArr; +}; + +/** + * Extracts and combines observation values from two imput 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 + */ +const createCombinedObservationValues = function (obsArrayOne, obsArrayTwo) { + // Extract the values from the two observation arrays + const obsValuesOne = obsArrayOne.map((result) => result[1]); + const obsValuesTwo = obsArrayTwo.map((result) => result[1]); + + // Since the arrays are of equal length, we need only use one of the arrays for looping + const obsValuesOnePlusTwo = obsValuesOne.map((obsValOne, i) => { + return [obsValOne, obsValuesTwo[i]]; + }); + + return obsValuesOnePlusTwo; +}; + +/** + * Format the response from SensorThings API to make it suitable for scatter plot + * @param {Array} obsArrayOne Response from SensorThings API as array + * @param {Array} obsArrayTwo Response from SensorThings API as array + * @returns {Array} Array of formatted observations suitable for use in a scatter plot + */ +const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) { + // When our observation arrays have DIFFERENT lengths + if (obsArrayOne.length !== obsArrayTwo.length) { + // Create arrays with timestamps only + const obsArrayOneTimestamp = obsArrayOne.map( + (obsTimeValue) => obsTimeValue[0] + ); + const obsArrayTwoTimestamp = obsArrayTwo.map( + (obsTimeValue) => obsTimeValue[0] + ); + + const missingTimestamp = getSymmetricDifferenceBetweenArrays( + obsArrayOneTimestamp, + obsArrayTwoTimestamp + ); + + // Determine the larger observation timestamp array + const biggerObsTimestampArr = getLargerArrayBetweenTwoInputArrays( + obsArrayOneTimestamp, + obsArrayTwoTimestamp + ); + + // Indexes of the missing observations + const indexesMissingObsArr = getIndexOfMissingObservation( + missingTimestamp, + biggerObsTimestampArr + ); + + // Determine the larger observation array + const biggerObsArr = getLargerArrayBetweenTwoInputArrays( + obsArrayOne, + obsArrayTwo + ); + + // Remove the missing observation from the larger array of observations + // Modifies the array in place + removeMissingObservationFromLargerArray(indexesMissingObsArr, biggerObsArr); + + return createCombinedObservationValues(obsArrayOne, obsArrayTwo); + } + + // When our observation arrays already have SAME lengths + return createCombinedObservationValues(obsArrayOne, obsArrayTwo); +}; + +/** + * 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 + * @returns {undefined} + */ +const drawScatterPlotHC = function ( + formattedObsArrayForSeriesOnePlusSeriesTwo, + formattedDatastreamMetadataSeriesOne, + formattedDatastreamMetadataSeriesTwo +) { + const { + datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_1, + datastreamName: DATASTREAM_NAME_SERIES_1, + phenomenonName: PHENOMENON_NAME_SERIES_1, + unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_1, + } = formattedDatastreamMetadataSeriesOne; + + const { + datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_2, + datastreamName: DATASTREAM_NAME_SERIES_2, + phenomenonName: PHENOMENON_NAME_SERIES_2, + unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_2, + } = formattedDatastreamMetadataSeriesTwo; + + // Order of axes + // Y-Axis -- Series 2 + // X-Axis -- Series 1 + + const CHART_TITLE = `${PHENOMENON_NAME_SERIES_2} Versus ${PHENOMENON_NAME_SERIES_1}`; + const CHART_SUBTITLE = `Source: ${DATASTREAM_NAME_SERIES_2} & ${DATASTREAM_NAME_SERIES_1}`; + + const SERIES_1_NAME = `${PHENOMENON_NAME_SERIES_1}`; + const SERIES_1_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_1}`; + + const SERIES_2_NAME = `${PHENOMENON_NAME_SERIES_2}`; + const SERIES_2_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_2}`; + + const SERIES_COMBINED_NAME = "Y, X"; + const SERIES_COMBINED_SYMBOL_COLOR_RGB_ELEMENTS = "223, 83, 83"; + const SERIES_COMBINED_SYMBOL_COLOR_OPACITY = ".3"; + const SERIES_COMBINED_SYMBOL_COLOR = `rgba(${SERIES_COMBINED_SYMBOL_COLOR_RGB_ELEMENTS}, ${SERIES_COMBINED_SYMBOL_COLOR_OPACITY})`; + + const MARKER_RADIUS = 2; + + Highcharts.chart("chart-scatter-plot", { + chart: { + type: "scatter", + zoomType: "xy", + }, + + boost: { + useGPUTranslations: true, + usePreAllocated: true, + }, + + title: { + text: CHART_TITLE, + }, + + subtitle: { + text: CHART_SUBTITLE, + }, + + xAxis: { + labels: { + format: `{value}`, + }, + title: { + enabled: true, + text: `${SERIES_1_NAME} [${SERIES_1_SYMBOL}]`, + }, + startOnTick: true, + endOnTick: true, + showLastLabel: true, + }, + + yAxis: [ + { + labels: { + format: `{value}`, + }, + title: { + text: `${SERIES_2_NAME} [${SERIES_2_SYMBOL}]`, + }, + }, + ], + + legend: { + enabled: false, + }, + + plotOptions: { + scatter: { + marker: { + radius: MARKER_RADIUS, + states: { + hover: { + enabled: true, + lineColor: "rgb(100,100,100)", + }, + }, + }, + states: { + hover: { + marker: { + enabled: false, + }, + }, + }, + tooltip: { + headerFormat: "{series.name}<br>", + pointFormat: `<b>{point.y:.2f} ${SERIES_1_SYMBOL}, {point.x:.2f} ${SERIES_2_SYMBOL}</b>`, + }, + }, + }, + + series: [ + { + name: SERIES_COMBINED_NAME, + color: SERIES_COMBINED_SYMBOL_COLOR, + data: formattedObsArrayForSeriesOnePlusSeriesTwo, + }, + ], + }); +}; + /** * Follows "@iot.nextLink" links in SensorThingsAPI's response * Appends new results to existing results @@ -498,23 +795,24 @@ const getCombinedObservationsFromAllNextLinks = function ( * @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 getMetadataPlusObservationsForChart = async function ( +const getMetadataPlusObservationsFromSingleDatastream = async function ( metadataPlusObsPromiseArray ) { - // Array to store our final result - const combinedResolvedPromises = []; + 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) { - try { + // 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); - } catch (err) { - console.error(err); } + + return combinedResolvedPromises; + } catch (err) { + console.error(err); } - return combinedResolvedPromises; }; /** @@ -526,398 +824,106 @@ const getMetadataPlusObservationsForChart = async function ( const getObservationsFromMultipleDatastreams = async function ( observationPromiseArray ) { - // Array to store our final result - const observationsAllDatastreamsArr = []; + try { + // Array to store our final result + const observationsAllDatastreamsArr = []; - // Use for/of loop - we need to maintain the order of execution of the async operations - for (const observationPromise of observationPromiseArray) { - try { + // Use for/of loop - we need to maintain the order of execution of the async operations + for (const observationPromise of observationPromiseArray) { // Observations from a single Datastream const observations = await observationPromise; observationsAllDatastreamsArr.push(observations); - } catch (err) { - console.error(err); } + + return observationsAllDatastreamsArr; + } catch (err) { + console.error(err); } - return observationsAllDatastreamsArr; }; -// Building + phenomenon + sampling rate -const buildingsSensorSamplingRateRLArr = [ - ["101", "rl", "60min"], - ["102", "rl", "60min"], - ["107", "rl", "60min"], - ["112, 118", "rl", "60min"], - ["125", "rl", "60min"], - ["225", "rl", "60min"], -]; - -// Datastreams IDs -const datastreamsRLArr = buildingsSensorSamplingRateRLArr.map((bldg) => - getDatastreamIdFromBuildingNumber(...bldg) -); - -// Datastreams URLs -const datastreamsUrlRLArr = datastreamsRLArr.map((datastreamId) => - getObservationsUrl(BASE_URL, datastreamId) -); - -// Promise objects - Observations / RL -const observationsPromisesRLArr = datastreamsUrlRLArr.map((obsUrl) => - getCombinedObservationsFromAllNextLinks( - axiosGetRequest(obsUrl, QUERY_PARAMS_COMBINED) - ) -); - -// getObservationsFromMultipleDatastreams(observationsPromisesRLArr).then((x) => -// console.log(x) -// ); - -const drawScatterPlotHC = function () { - // Ruecklauf - const ruecklaufArr = [ - [1578193200000, 69.1999969482422], - [1578196800000, 69.1999969482422], - [1578200400000, 69.1999969482422], - [1578204000000, 69.1999969482422], - [1578207600000, 69.1999969482422], - [1578211200000, 69.1999969482422], - [1578214800000, 69.1999969482422], - [1578218400000, 69.1999969482422], - [1578222000000, 69.1999969482422], - [1578225600000, 69.1999969482422], - [1578229200000, 69.1999969482422], - [1578232800000, 69.1999969482422], - [1578236400000, 69.1999969482422], - [1578240000000, 69.1999969482422], - [1578243600000, 69.1999969482422], - [1578247200000, 69.1999969482422], - [1578250800000, 69.1999969482422], - [1578254400000, 69.1999969482422], - [1578258000000, 69.1999969482422], - [1578261600000, 69.1999969482422], - [1578265200000, 69.1999969482422], - [1578268800000, 69.1999969482422], - [1578272400000, 69.1999969482422], - [1578276000000, 69.1999969482422], - [1578279600000, 69.1999969482422], - [1578283200000, 69.1999969482422], - [1578286800000, 69.1999969482422], - [1578290400000, 69.1999969482422], - [1578294000000, 69.1999969482422], - [1578297600000, 69.1999969482422], - [1578301200000, 69.1999969482422], - [1578304800000, 69.1999969482422], - [1578308400000, 69.1999969482422], - [1578312000000, 69.1999969482422], - [1578315600000, 69.1999969482422], - [1578319200000, 69.1999969482422], - [1578322800000, 69.1999969482422], - [1578326400000, 69.1999969482422], - [1578330000000, 69.1999969482422], - [1578333600000, 69.1999969482422], - [1578337200000, 69.1999969482422], - [1578340800000, 69.1999969482422], - [1578344400000, 69.1999969482422], - [1578348000000, 69.1999969482422], - [1578351600000, 70.3556109079997], - [1578355200000, 76.920997634146], - [1578358800000, 79.4292947098697], - [1578362400000, 79.2650916245309], - [1578366000000, 79.6125674172757], - [1578369600000, 79.0597236964905], - [1578373200000, 77.7484098868052], - [1578376800000, 77.1226613864899], - [1578380400000, 76.9194480415149], - [1578384000000, 78.0028471359237], - [1578387600000, 77.1535494270819], - [1578391200000, 75.0470741498029], - [1578394800000, 74.679502580818], - [1578398400000, 73.6077361986314], - [1578402000000, 72.5580677314758], - [1578405600000, 72.3134755830553], - [1578409200000, 73.2778338997311], - [1578412800000, 73.7656394293467], - [1578416400000, 74.4736907299466], - [1578420000000, 74.046935040758], - [1578423600000, 73.4957105572807], - [1578427200000, 73.9627712815163], - [1578430800000, 74.0438044241729], - [1578434400000, 73.3727496036106], - [1578438000000, 73.1666655679279], - [1578441600000, 73.3418058388816], - [1578445200000, 73.6250001589457], - [1578448800000, 73.829112378629], - [1578452400000, 74.564083528116], - [1578456000000, 75.5183061171072], - [1578459600000, 77.3372781058983], - [1578463200000, 78.0196371225993], - [1578466800000, 77.6398578971368], - [1578470400000, 78.5464104081542], - [1578474000000, 78.7977605936686], - [1578477600000, 76.0006624035588], - [1578481200000, 74.818987728345], - [1578484800000, 72.7776491559135], - [1578488400000, 71.1266380795161], - [1578492000000, 71.3866485616896], - [1578495600000, 72.1584558128357], - [1578499200000, 72.7288795283423], - [1578502800000, 73.2401491424669], - [1578506400000, 72.613320930343], - [1578510000000, 71.7903886201647], - [1578513600000, 71.4483344078064], - [1578517200000, 71.8162703686467], - [1578520800000, 71.3690680013303], - [1578524400000, 70.6132688085203], - [1578528000000, 69.9669739277875], - [1578531600000, 69.2502318650422], - [1578535200000, 68.8407318482576], - [1578538800000, 71.4223982252898], - [1578542400000, 68.8941290716666], - [1578546000000, 71.8311421724037], - [1578549600000, 72.5245706435945], - ]; +/** + * 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 + */ +const getMetadataPlusObservationsFromMultipleDatastreams = async function ( + bldgSensorSamplingRateArr +) { + try { + if (!bldgSensorSamplingRateArr) return; + + // Datastreams IDs + const datastreamsIdsArr = bldgSensorSamplingRateArr.map( + (bldgSensorSamplingRate) => + getDatastreamIdFromBuildingNumber(...bldgSensorSamplingRate) + ); + + // Observations URLs + const observationsUrlArr = datastreamsIdsArr.map((datastreamId) => + getObservationsUrl(BASE_URL, datastreamId) + ); + + // Datastreams URLs + const datastreamsUrlArr = datastreamsIdsArr.map((datastreamId) => + getDatastreamUrl(BASE_URL, datastreamId) + ); + + // Promise objects - Observations + const observationsPromisesArr = observationsUrlArr.map((obsUrl) => + getCombinedObservationsFromAllNextLinks( + axiosGetRequest(obsUrl, QUERY_PARAMS_COMBINED) + ) + ); + + // Observations array + const observationsArr = await getObservationsFromMultipleDatastreams( + observationsPromisesArr + ); + + // Metadata array + const metadataArr = await getMetadataFromMultipleDatastreams( + datastreamsUrlArr + ); + + return [observationsArr, metadataArr]; + } catch (err) { + console.error(err); + } +}; - // Power - const powerArr = [ - [1578193200000, 0], - [1578196800000, 0], - [1578200400000, 0], - [1578204000000, 0], - [1578207600000, 0], - [1578211200000, 0], - [1578214800000, 0], - [1578218400000, 0], - [1578222000000, 0], - [1578225600000, 0], - [1578229200000, 0], - [1578232800000, 0], - [1578236400000, 0], - [1578240000000, 0], - [1578243600000, 0], - [1578247200000, 0], - [1578250800000, 0], - [1578254400000, 0], - [1578258000000, 0], - [1578261600000, 0], - [1578265200000, 0], - [1578268800000, 0], - [1578272400000, 0], - [1578276000000, 0], - [1578279600000, 0], - [1578283200000, 0], - [1578286800000, 0], - [1578290400000, 0], - [1578294000000, 0], - [1578297600000, 0], - [1578301200000, 0], - [1578304800000, 0], - [1578308400000, 0], - [1578312000000, 0], - [1578315600000, 0], - [1578319200000, 0], - [1578322800000, 0], - [1578326400000, 0], - [1578330000000, 0], - [1578333600000, 0], - [1578337200000, 0], - [1578340800000, 0], - [1578344400000, 0], - [1578348000000, 0], - [1578351600000, 0.831280025800069], - [1578355200000, 27.4361266860337], - [1578358800000, 4.02296011930285], - [1578362400000, 5.46578637448993], - [1578366000000, 189.045738115567], - [1578369600000, 262.879154692536], - [1578373200000, 182.996291840137], - [1578376800000, 253.720326864073], - [1578380400000, 266.71791350888], - [1578384000000, 258.650130305165], - [1578387600000, 256.817462126146], - [1578391200000, 251.198874591439], - [1578394800000, 245.782954276794], - [1578398400000, 225.835229413786], - [1578402000000, 191.164833256192], - [1578405600000, 189.317473084174], - [1578409200000, 160.866751228135], - [1578412800000, 165.104705085896], - [1578416400000, 185.380724406267], - [1578420000000, 6.318082232318], - [1578423600000, 22.6244981930396], - [1578427200000, 0.125080846609247], - [1578430800000, 0.858333364129066], - [1578434400000, 3.15562303745482], - [1578438000000, 1.73965485449897], - [1578441600000, 3.73938900530338], - [1578445200000, 0.641666680574417], - [1578448800000, 1.64397225697835], - [1578452400000, 14.7165156847371], - [1578456000000, 6.7406491904815], - [1578459600000, 257.018884414906], - [1578463200000, 282.409075120573], - [1578466800000, 284.999958205159], - [1578470400000, 291.20836768991], - [1578474000000, 285.753944205729], - [1578477600000, 248.43810322171], - [1578481200000, 227.135268204399], - [1578484800000, 182.10778157076], - [1578488400000, 169.076414526325], - [1578492000000, 160.098294117384], - [1578495600000, 149.832191919638], - [1578499200000, 195.966023142751], - [1578502800000, 159.01891281008], - [1578506400000, 3.94323859943668], - [1578510000000, 9.29238140483663], - [1578513600000, 4.09348021179692], - [1578517200000, 0], - [1578520800000, 0], - [1578524400000, 0], - [1578528000000, 0], - [1578531600000, 0], - [1578535200000, 0], - [1578538800000, 154.315364068476], - [1578542400000, 193.405831769548], - [1578546000000, 136.484141248209], - [1578549600000, 209.041194383494], +/** + * Test drawing of scatter plot chart + */ +const drawScatterPlotHCTest2 = async function () { + const sensorsOfInterestArr = [ + ["225", "vl", "60min"], + // ["125", "rl", "60min"], + ["weather_station_521", "outside_temp", "60min"], ]; - const CHART_TITLE = "Height Versus Weight of 507 Individuals by Gender"; - const CHART_SUBTITLE = "Source: Heinz 2003"; - - const X_AXIS_TITLE = "Height (cm)"; - - const SERIES_1_NAME = "Rücklauftemp"; - const SERIES_1_SYMBOL_COLOR = "rgba(119, 152, 191, .5)"; - const SERIES_1_TEXT_COLOR = "rgb(119, 152, 191)"; // remove transparency from symbol color for a more "intense" color - const SERIES_1_SYMBOL = "°C"; - - const SERIES_2_NAME = "Power"; - const SERIES_2_SYMBOL_COLOR = "rgba(223, 83, 83, .5)"; - const SERIES_2_TEXT_COLOR = "rgb(223, 83, 83)"; // remove transparency from symbol color for a more "intense" color - const SERIES_2_SYMBOL = "kW"; - - Highcharts.chart("chart-scatter-plot", { - chart: { - type: "scatter", - zoomType: "xy", - }, - - title: { - text: CHART_TITLE, - }, - - subtitle: { - text: CHART_SUBTITLE, - }, - - xAxis: { - title: { - enabled: true, - text: X_AXIS_TITLE, - }, - type: "datetime", - startOnTick: true, - endOnTick: true, - showLastLabel: true, - }, - - yAxis: [ - { - // Primary yAxis - labels: { - format: `{value} ${SERIES_1_SYMBOL}`, - style: { - color: SERIES_1_TEXT_COLOR, - }, - }, - title: { - text: SERIES_1_NAME, - style: { - color: SERIES_1_TEXT_COLOR, - }, - }, - }, - { - // Secondary yAxis - title: { - text: SERIES_2_NAME, - style: { - color: SERIES_2_TEXT_COLOR, - }, - }, - labels: { - format: `{value} ${SERIES_2_SYMBOL}`, - style: { - color: SERIES_2_TEXT_COLOR, - }, - }, - opposite: true, - }, - ], - - legend: { - layout: "vertical", - align: "left", - verticalAlign: "top", - x: 100, - y: 70, - floating: true, - backgroundColor: Highcharts.defaultOptions.chart.backgroundColor, - borderWidth: 1, - }, - - plotOptions: { - scatter: { - marker: { - radius: 5, - states: { - hover: { - enabled: true, - lineColor: "rgb(100,100,100)", - }, - }, - }, - states: { - hover: { - marker: { - enabled: false, - }, - }, - }, - tooltip: { - headerFormat: "{point.x:%e %b, %Y %H:%M:%S}: <b>{point.y}</b>", - pointFormat: "{point.x} cm, {point.y} kg", - valueDecimals: 2, - }, - }, - }, - - series: [ - { - name: SERIES_1_NAME, - color: SERIES_1_SYMBOL_COLOR, - data: ruecklaufArr, - tooltip: { - valueSuffix: ` ${SERIES_1_SYMBOL}`, - }, - }, - { - name: SERIES_2_NAME, - color: SERIES_2_SYMBOL_COLOR, - data: powerArr, - // need this property for the dual y-axes to work - // defines the y-axis that this series refers to - yAxis: 1, - tooltip: { - valueSuffix: ` ${SERIES_2_SYMBOL}`, - }, - }, - ], - }); + const observationsPlusMetadata = + await getMetadataPlusObservationsFromMultipleDatastreams( + sensorsOfInterestArr + ); + + // Extract the observations and metadata for each sensor + // Array elements in same order as input array + const [ + [obsSensorOneArr, obsSensorTwoArr], + [metadataSensorOne, metadataSensorTwo], + ] = observationsPlusMetadata; + + drawScatterPlotHC( + formatSTAResponseForScatterPlot(obsSensorOneArr, obsSensorTwoArr), + formatDatastreamMetadataForChart(metadataSensorOne), + formatDatastreamMetadataForChart(metadataSensorTwo) + ); }; -drawScatterPlotHC(); +(async () => { + await drawScatterPlotHCTest2(); +})(); export { BASE_URL, @@ -927,12 +933,12 @@ export { getObservationsUrl, createTemporalFilterString, axiosGetRequest, - getDatastreamMetadata, + getMetadataFromSingleDatastream, formatDatastreamMetadataForChart, formatSTAResponseForHeatMap, drawHeatMapHC, - formatSTAResponseForLineChart, + formatSTAResponseForLineChartOrScatterPlot, drawLineChartHC, getCombinedObservationsFromAllNextLinks, - getMetadataPlusObservationsForChart, + getMetadataPlusObservationsFromSingleDatastream, }; diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js index 8b72d4489d87637f3c1a2bba7f7f1cda3a2c9ec2..ac3a750c2a202da17d00e82360584771320082ca 100644 --- a/public/js/dropDownList.js +++ b/public/js/dropDownList.js @@ -7,14 +7,14 @@ import { getDatastreamUrl, getObservationsUrl, axiosGetRequest, - getDatastreamMetadata, + getMetadataFromSingleDatastream, formatDatastreamMetadataForChart, formatSTAResponseForHeatMap, drawHeatMapHC, - formatSTAResponseForLineChart, + formatSTAResponseForLineChartOrScatterPlot, drawLineChartHC, getCombinedObservationsFromAllNextLinks, - getMetadataPlusObservationsForChart, + getMetadataPlusObservationsFromSingleDatastream, } from "./appChart.js"; const buildingsAvailableSensorsArr = [ @@ -289,16 +289,18 @@ const selectChartTypeFromDropDown = async function () { const URL_OBSERVATIONS = getObservationsUrl(BASE_URL, selectedDatastream); // Create promises - const promiseDatastreamMetadata = getDatastreamMetadata(URL_DATASTREAM); + const promiseDatastreamMetadata = + getMetadataFromSingleDatastream(URL_DATASTREAM); const promiseCombinedObservations = getCombinedObservationsFromAllNextLinks( axiosGetRequest(URL_OBSERVATIONS, QUERY_PARAMS_COMBINED) ); // Pass promises to our async function - const metadataPlusObservations = await getMetadataPlusObservationsForChart([ - promiseCombinedObservations, - promiseDatastreamMetadata, - ]); + const metadataPlusObservations = + await getMetadataPlusObservationsFromSingleDatastream([ + promiseCombinedObservations, + promiseDatastreamMetadata, + ]); // Extract the metadata and the observations from resulting array const combinedObs = metadataPlusObservations[0]; @@ -306,7 +308,7 @@ const selectChartTypeFromDropDown = async function () { if (selectedChartType === "Line") { drawLineChartHC( - formatSTAResponseForLineChart(combinedObs), + formatSTAResponseForLineChartOrScatterPlot(combinedObs), formatDatastreamMetadataForChart(datastreamMetadata) ); } else if (selectedChartType === "Heatmap") {