Commit 786a5e18 authored by Pithon Kabiro's avatar Pithon Kabiro
Browse files

Draw column chart

It is assumed that this chart type will be used for visualizing
aggregated results
parent 1c71824b
...@@ -28,12 +28,11 @@ ...@@ -28,12 +28,11 @@
></script> ></script>
<!-- Axios --> <!-- Axios -->
<!-- <script src="./node_modules/axios/dist/axios.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!-- Higcharts lib --> <!-- Higcharts -->
<!-- Does not play well with `Highstock`; see: https://www.highcharts.com/errors/16/ <!-- `highcharts.js` does not play well with `highstock.js`; see: https://www.highcharts.com/errors/16/-->
<script src="https://code.highcharts.com/highcharts.js"></script> --> <!-- <script src="https://code.highcharts.com/highcharts.js"></script> -->
<script src="https://code.highcharts.com/stock/highstock.js"></script> <script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/stock/modules/data.js"></script> <script src="https://code.highcharts.com/stock/modules/data.js"></script>
<script src="https://code.highcharts.com/stock/modules/exporting.js"></script> <script src="https://code.highcharts.com/stock/modules/exporting.js"></script>
...@@ -44,11 +43,7 @@ ...@@ -44,11 +43,7 @@
<script src="https://code.highcharts.com/modules/boost.js"></script> <script src="https://code.highcharts.com/modules/boost.js"></script>
<script src="https://code.highcharts.com/modules/accessibility.js"></script> <script src="https://code.highcharts.com/modules/accessibility.js"></script>
<!-- Apexcharts lib --> <!-- Cesium -->
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<link rel="stylesheet" href="css/styles.css" />
<!-- Cesium lib -->
<script src="https://cesium.com/downloads/cesiumjs/releases/1.48/Build/Cesium/Cesium.js"></script> <script src="https://cesium.com/downloads/cesiumjs/releases/1.48/Build/Cesium/Cesium.js"></script>
<link <link
href="https://cesium.com/downloads/cesiumjs/releases/1.48/Build/Cesium/Widgets/widgets.css" href="https://cesium.com/downloads/cesiumjs/releases/1.48/Build/Cesium/Widgets/widgets.css"
...@@ -230,7 +225,7 @@ ...@@ -230,7 +225,7 @@
Bar Chart Example Bar Chart Example
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="chart-bar" width="100%" height="40"></div> <div id="chart-column" width="100%" height="40"></div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -18,14 +18,6 @@ ...@@ -18,14 +18,6 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
}, },
"axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"body-parser": { "body-parser": {
"version": "1.19.0", "version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
...@@ -160,11 +152,6 @@ ...@@ -160,11 +152,6 @@
"unpipe": "~1.0.0" "unpipe": "~1.0.0"
} }
}, },
"follow-redirects": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
"integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA=="
},
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^0.21.1",
"express": "^4.17.1" "express": "^4.17.1"
} }
} }
...@@ -17,8 +17,14 @@ import { ...@@ -17,8 +17,14 @@ import {
drawScatterPlotHighcharts, drawScatterPlotHighcharts,
} from "./src_modules/chartScatterPlot.js"; } from "./src_modules/chartScatterPlot.js";
import {
formatAggregationResultForColumnChart,
drawColumnChartHighcharts,
} from "./src_modules/chartColumn.js";
import { import {
formatDatastreamMetadataForChart, formatDatastreamMetadataForChart,
extractPropertiesFromFormattedDatastreamMetadata,
getMetadataPlusObservationsFromSingleOrMultipleDatastreams, getMetadataPlusObservationsFromSingleOrMultipleDatastreams,
calculateVorlaufMinusRuecklaufTemperature, calculateVorlaufMinusRuecklaufTemperature,
} from "./src_modules/fetchData.js"; } from "./src_modules/fetchData.js";
...@@ -34,7 +40,7 @@ import { ...@@ -34,7 +40,7 @@ import {
* Test plotting of temp difference (dT) using heatmap * Test plotting of temp difference (dT) using heatmap
*/ */
const drawHeatmapHCUsingTempDifference = async function () { const drawHeatmapHCUsingTempDifference = async function () {
const [tempDifferenceObsArrBau225, tempDifferenceMetadataBau225] = const [observationsTemperatureDiff225Arr, metadataTemperatureDiff225Arr] =
await calculateVorlaufMinusRuecklaufTemperature( await calculateVorlaufMinusRuecklaufTemperature(
BASE_URL, BASE_URL,
QUERY_PARAMS_COMBINED, QUERY_PARAMS_COMBINED,
...@@ -42,9 +48,36 @@ const drawHeatmapHCUsingTempDifference = async function () { ...@@ -42,9 +48,36 @@ const drawHeatmapHCUsingTempDifference = async function () {
"60min" "60min"
); );
// We want to have nested arrays, so as to mimick the nested responses we get from fetching observations + metadata
const observationsTemperatureDiff225NestedArr = [
observationsTemperatureDiff225Arr,
];
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
);
// First need to extract the formatted observations from the nested array
// Heatmap only needs one set of formatted observation values
drawHeatMapHighcharts( drawHeatMapHighcharts(
formatSensorThingsApiResponseForHeatMap(tempDifferenceObsArrBau225), ...formattedTempDiff225NestedArr,
formatDatastreamMetadataForChart(tempDifferenceMetadataBau225) extractedFormattedTempDiff225Properties
); );
}; };
...@@ -52,7 +85,7 @@ const drawHeatmapHCUsingTempDifference = async function () { ...@@ -52,7 +85,7 @@ const drawHeatmapHCUsingTempDifference = async function () {
* Test drawing of scatter plot chart * Test drawing of scatter plot chart
*/ */
const drawScatterPlotHCTest2 = async function () { const drawScatterPlotHCTest2 = async function () {
const sensorsOfInterestArr = [ const sensorsOfInterestNestedArr = [
["225", "vl", "60min"], ["225", "vl", "60min"],
// ["125", "rl", "60min"], // ["125", "rl", "60min"],
["weather_station_521", "outside_temp", "60min"], ["weather_station_521", "outside_temp", "60min"],
...@@ -62,26 +95,32 @@ const drawScatterPlotHCTest2 = async function () { ...@@ -62,26 +95,32 @@ const drawScatterPlotHCTest2 = async function () {
await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
BASE_URL, BASE_URL,
QUERY_PARAMS_COMBINED, QUERY_PARAMS_COMBINED,
sensorsOfInterestArr sensorsOfInterestNestedArr
); );
// Extract the combined arrays for observations and metadata // Extract the combined arrays for observations and metadata
const [observationsArr, metadataArr] = observationsPlusMetadata; const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
// Create formatted array(s) for observations // Create formatted array(s) for observations
// This function expects two arguments, these are unpacked using the spread operator // This function expects two arguments, these are unpacked using the spread operator
const formattedObsScatterPlotArr = const formattedObservationsArr = formatSensorThingsApiResponseForScatterPlot(
formatSensorThingsApiResponseForScatterPlot(...observationsArr); ...observationsNestedArr
);
// Create formatted array(s) for metadata // Create formatted array(s) for metadata
const formattedMetadataArr = metadataArr.map((metadata) => const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
formatDatastreamMetadataForChart(metadata) formatDatastreamMetadataForChart(metadataObj)
); );
// This function expects three arguments, the second and third are unpacked using the spread operator // Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr
);
drawScatterPlotHighcharts( drawScatterPlotHighcharts(
formattedObsScatterPlotArr, formattedObservationsArr,
...formattedMetadataArr extractedFormattedDatastreamProperties
); );
}; };
...@@ -89,84 +128,132 @@ const drawScatterPlotHCTest2 = async function () { ...@@ -89,84 +128,132 @@ const drawScatterPlotHCTest2 = async function () {
* Test drawing of line chart with multiple series * Test drawing of line chart with multiple series
*/ */
const testLineChartMultipleSeries = async function () { const testLineChartMultipleSeries = async function () {
const sensorsOfInterestArr = [ const sensorsOfInterestNestedArr = [
["225", "vl", "60min"], ["225", "vl", "60min"],
["125", "rl", "60min"], ["125", "rl", "60min"],
["weather_station_521", "outside_temp", "60min"], ["weather_station_521", "outside_temp", "60min"],
]; ];
const observationsPlusMetadata = const observationsPlusMetadataArr =
await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
BASE_URL, BASE_URL,
QUERY_PARAMS_COMBINED, QUERY_PARAMS_COMBINED,
sensorsOfInterestArr sensorsOfInterestNestedArr
); );
// Extract the observations and metadata arrays // Extract the observations and metadata arrays of arrays
const [observationsArr, metadataArr] = observationsPlusMetadata; const [observationsNestedArr, metadataNestedArr] =
observationsPlusMetadataArr;
// Format the observations and metadata // Format the observations
const formattedObservationsArr = observationsArr.map((observations) => const formattedObservationsNestedArr = observationsNestedArr.map(
formatSensorThingsApiResponseForLineChart(observations) (observationsArr) =>
formatSensorThingsApiResponseForLineChart(observationsArr)
); );
const formattedMetadataArr = metadataArr.map((metadata) => // Format the metadata
formatDatastreamMetadataForChart(metadata) const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) =>
formatDatastreamMetadataForChart(metadataArr)
); );
drawLineChartHighcharts(formattedObservationsArr, formattedMetadataArr); // Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr
);
drawLineChartHighcharts(
formattedObservationsNestedArr,
extractedFormattedDatastreamProperties
);
}; };
/** /**
* Test aggregation of observations from a single datastream * Test drawing of column chart using aggregation result
*/ */
const testAggregationSum = async function () { const drawColumnChartMonthlySumTest = async function () {
const sensorOfInterestNestedArr = [["225", "vl", "60min"]]; const sensorsOfInterestNestedArr = [
["125", "vl", "60min"],
["225", "vl", "60min"],
];
const observationsPlusMetadata = const observationsPlusMetadata =
await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
BASE_URL, BASE_URL,
QUERY_PARAMS_COMBINED, QUERY_PARAMS_COMBINED,
sensorOfInterestNestedArr sensorsOfInterestNestedArr
); );
// Extract the observations and metadata for each sensor // Extract the observations and metadata for each sensor
// Array elements in same order as input array // Array elements in same order as input array
const [[obsSensorOneArr], [metadataSensorOne]] = observationsPlusMetadata; const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
// Unique calendar dates // Unique calendar dates
const uniqueCalendarDates = const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
extractUniqueCalendarDatesFromTimestamp(obsSensorOneArr); (observationsArr) =>
extractUniqueCalendarDatesFromTimestamp(observationsArr)
);
// Unique calendar months // Unique calendar months
const uniqueCalendarMonths = const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDates); (uniqueCalendarDatesArr) =>
extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
);
// Calculate sum of values of observations - daily // Calculate sum of values of observations - daily
const observationsBau225VLSumDaily = uniqueCalendarDates.map((calendarDate) => // Note the two nested `map` methods
calculateSumOfObservationValuesWithinDatesInterval( const observationsSumDailyNestedArr = uniqueCalendarDatesNestedArr.map(
obsSensorOneArr, (uniqueCalendarDatesArr, i) =>
"60 min", uniqueCalendarDatesArr.map((uniqueCalendarDate) =>
calendarDate, calculateSumOfObservationValuesWithinDatesInterval(
calendarDate observationsNestedArr[i],
) "60 min",
uniqueCalendarDate,
uniqueCalendarDate
)
)
); );
// Calculate sum of values of observations - monthly // Calculate sum of values of observations - monthly
const observationsBau225VLSumMonthly = uniqueCalendarMonths.map( // Note the two nested `map` methods
(calendarMonth) => const observationsSumMonthlyNestedArr = uniqueCalendarMonthsNestedArr.map(
calculateSumOfObservationValuesWithinMonthInterval( (uniqueCalendarMonthsArr, i) =>
obsSensorOneArr, uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
"60 min", calculateSumOfObservationValuesWithinMonthInterval(
calendarMonth observationsNestedArr[i],
"60 min",
uniqueCalendarMonth
)
) )
); );
console.log(observationsBau225VLSumDaily); // Format the observations
console.log(observationsBau225VLSumMonthly); const formattedObservationsNestedArr = 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
);
drawColumnChartHighcharts(
formattedObservationsNestedArr,
extractedFormattedDatastreamProperties
);
}; };
// drawScatterPlotHCTest2(); // drawScatterPlotHCTest2();
// drawHeatmapHCUsingTempDifference(); // drawHeatmapHCUsingTempDifference();
// testLineChartMultipleSeries() // testLineChartMultipleSeries();
// testAggregationSum(); // drawColumnChartMonthlySumTest();
...@@ -4,6 +4,7 @@ import { BASE_URL, QUERY_PARAMS_COMBINED } from "./src_modules/createUrl.js"; ...@@ -4,6 +4,7 @@ import { BASE_URL, QUERY_PARAMS_COMBINED } from "./src_modules/createUrl.js";
import { import {
formatDatastreamMetadataForChart, formatDatastreamMetadataForChart,
extractPropertiesFromFormattedDatastreamMetadata,
getMetadataPlusObservationsFromSingleOrMultipleDatastreams, getMetadataPlusObservationsFromSingleOrMultipleDatastreams,
} from "./src_modules/fetchData.js"; } from "./src_modules/fetchData.js";
...@@ -272,28 +273,41 @@ const selectChartTypeFromDropDown = async function () { ...@@ -272,28 +273,41 @@ const selectChartTypeFromDropDown = async function () {
); );
// Extract the combined arrays for observations and metadata // Extract the combined arrays for observations and metadata
const [observationsArr, metadataArr] = observationsPlusMetadata; const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
// Create formatted array(s) for observations - line chart // Create formatted array(s) for observations - line chart
const formattedObsLineChartArr = observationsArr.map((observations) => const formattedObsLineChartArr = observationsNestedArr.map(
formatSensorThingsApiResponseForLineChart(observations) (observationsArr) =>
formatSensorThingsApiResponseForLineChart(observationsArr)
); );
// Create formatted array(s) for observations - heatmap // Create formatted array(s) for observations - heatmap
const formattedObsHeatMapArr = observationsArr.map((observations) => const formattedObsHeatMapArr = observationsNestedArr.map(
formatSensorThingsApiResponseForHeatMap(observations) (observationsArr) =>
formatSensorThingsApiResponseForHeatMap(observationsArr)
); );
// Create formatted array(s) for metadata - same for both chart types // Create formatted array(s) for metadata - same for both chart types
const formattedMetadataArr = metadataArr.map((metadata) => const formattedMetadataArr = metadataNestedArr.map((metadataObj) =>
formatDatastreamMetadataForChart(metadata) formatDatastreamMetadataForChart(metadataObj)
); );
// Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(formattedMetadataArr);
if (selectedChartType === "Line") { if (selectedChartType === "Line") {
drawLineChartHighcharts(formattedObsLineChartArr, formattedMetadataArr); drawLineChartHighcharts(
formattedObsLineChartArr,
extractedFormattedDatastreamProperties
);
} else if (selectedChartType === "Heatmap") { } else if (selectedChartType === "Heatmap") {
// First need to extract the nested arrays for the formatted observations and metadata // First need to extract the formatted observations from the nested array
drawHeatMapHighcharts(...formattedObsHeatMapArr, ...formattedMetadataArr); // Heatmap only needs one set of formatted observation values
drawHeatMapHighcharts(
...formattedObsHeatMapArr,
extractedFormattedDatastreamProperties
);
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
......
/**
* Format a computed aggregation result to make it suitable for a column chart
* @param {Array} calendarDatesMonthsStrArr An array of unique calendar dates strings (in "YYYY-MM-DD" fromat) or unique calendar months strings (in "YYYY-MM" format)
* @param {Array} aggregatedValuesArr An array of aggregated values
* @returns {Array} An array of formatted aggregation values suitable for use in a column chart
*/
const formatAggregationResultForColumnChart = function (
calendarDatesMonthsStrArr,
aggregatedValuesArr
) {
if (!calendarDatesMonthsStrArr || !aggregatedValuesArr) return;
// Create an array of Unix timestamp strings
const timestampsArr = calendarDatesMonthsStrArr.map((calendarStr) =>
new Date(calendarStr).getTime()
);
// Combine timestamp and value pairs
// The timestamps array and values array have same lengths, use one for looping
return timestampsArr.map((timestamp, i) => [
timestamp,
aggregatedValuesArr[i],
]);
};
/**
* Creates an options object for each series drawn in a column chart
* @param {Array} formattedAggregatedResultForColumnChart An array of formatted aggregated result array(s) from one or more datastreams
* @param {Array} phenomenonNamesArr An array of phenomenon name(s)
* @param {Array} phenomenonSymbolsArr An array of phenomenon symbol(s)
* @returns {Array} An array made up of series options object(s)
*/
const createSeriesOptionsForColumnChart = function (
formattedAggregatedResultForColumnChart,
phenomenonNamesArr,
phenomenonSymbolsArr
) {
// 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 formattedAggregatedResultForColumnChart.map(
(formattedAggResArray, i) => {
return {
name: `${phenomenonNamesArr[i]} (${phenomenonSymbolsArr[i]})`,
data: formattedAggResArray,
turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
};
}
);
};
/**
* Draw a column chart using Highcharts library
* @param {Array} formattedAggResultArraysForColumnChart An array made up of formatted aggregated result array(s) suitable for use in a column chart
* @param {Object} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties
* @returns {undefined}
*/
const drawColumnChartHighcharts = function (
formattedAggResultArraysForColumnChart,
extractedFormattedDatastreamProperties
) {
// Arrays of datastream properties
const {
datastreamNamesArr,
phenomenonNamesArr,
unitOfMeasurementSymbolsArr,
} = extractedFormattedDatastreamProperties;
const seriesOptionsArr = createSeriesOptionsForColumnChart(
formattedAggResultArraysForColumnChart,
phenomenonNamesArr,
unitOfMeasurementSymbolsArr
);
Highcharts.chart("chart-column", {
chart: {
type: "column",
zoomType: "x",
},
title: {
text: "Monthly Average Rainfall",
},
subtitle: {
text: "Source: WorldClimate.com",
},
xAxis: {
type: "datetime",
crosshair: true,
},
yAxis: {
min: 0,
title: {
text: "Rainfall (mm)",
},
},
tooltip: {
headerFormat: `
<span style="font-size:10px">{point.key}</span>
<table>
`,
pointFormat: `
<tr>
<td style="color:{series.color};padding:0">{series.name}: </td>
<td style="padding:0"><b>{point.y:.1f} mm</b></td>
</tr>
`,
footerFormat: `
</table>
`,
shared: true,
useHTML: true,
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0,
},
},
series: seriesOptionsArr,
});
};
export { formatAggregationResultForColumnChart, drawColumnChartHighcharts };
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
const formatSensorThingsApiResponseForHeatMap = function (obsArray) { const formatSensorThingsApiResponseForHeatMap = function (obsArray) {
if (!obsArray) return; if (!obsArray) return;
const dataSTAFormatted = obsArray.map((obs) => { return obsArray.map((obs) => {
// Get the date/time string; first element in input array; remove trailing "Z" // Get the date/time string; first element in input array; remove trailing "Z"
const obsDateTimeInput = obs[0].slice(0, -1); const obsDateTimeInput = obs[0].slice(0, -1);
// Get the "date" part of an observation // Get the "date" part of an observation
...@@ -24,8 +24,6 @@ const formatSensorThingsApiResponseForHeatMap = function (obsArray) { ...@@ -24,8 +24,6 @@ const formatSensorThingsApiResponseForHeatMap = function (obsArray) {
const value = obs[1]; const value = obs[1];
return [timestamp, hourOfDay, value]; return [timestamp, hourOfDay, value];
}); });
return dataSTAFormatted;
}; };
/** /**
...@@ -53,19 +51,25 @@ const calculateMinMaxValuesForHeatmapColorAxis = function ( ...@@ -53,19 +51,25 @@ const calculateMinMaxValuesForHeatmapColorAxis = function (
/** /**
* Draw a heatmap using Highcharts library * Draw a heatmap using Highcharts library
* @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap * @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap
* @param {Object} formattedDatastreamMetadata Object containing Datastream metadata * @param {Object} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties
* @returns {undefined} undefined * @returns {undefined} undefined
*/ */
const drawHeatMapHighcharts = function ( const drawHeatMapHighcharts = function (
formattedObsArrayForHeatmap, formattedObsArrayForHeatmap,
formattedDatastreamMetadata extractedFormattedDatastreamProperties
) { ) {
// Arrays of datastream properties
const { const {
datastreamDescription: DATASTREAM_DESCRIPTION, datastreamDescriptionsArr,
datastreamName: DATASTREAM_NAME, datastreamNamesArr,
phenomenonName: PHENOMENON_NAME, phenomenonNamesArr,
unitOfMeasurementSymbol: PHENOMENON_SYMBOL, unitOfMeasurementSymbolsArr,
} = formattedDatastreamMetadata; } = extractedFormattedDatastreamProperties;
const [DATASTREAM_DESCRIPTION] = datastreamDescriptionsArr;
const [DATASTREAM_NAME] = datastreamNamesArr;
const [PHENOMENON_NAME] = phenomenonNamesArr;
const [PHENOMENON_SYMBOL] = unitOfMeasurementSymbolsArr;
const { const {
minObsValue: MINIMUM_VALUE_COLOR_AXIS, minObsValue: MINIMUM_VALUE_COLOR_AXIS,
......
...@@ -8,46 +8,11 @@ ...@@ -8,46 +8,11 @@
const formatSensorThingsApiResponseForLineChart = function (obsArray) { const formatSensorThingsApiResponseForLineChart = function (obsArray) {
if (!obsArray) return; if (!obsArray) return;
const dataSTAFormatted = obsArray.map((result) => { return obsArray.map((result) => {
const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp
const valueObs = result[1]; const valueObs = result[1];
return [timestampObs, valueObs]; return [timestampObs, valueObs];
}); });
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,
};
}; };
/** /**
...@@ -104,19 +69,19 @@ const createSeriesOptionsForLineChart = function ( ...@@ -104,19 +69,19 @@ const createSeriesOptionsForLineChart = function (
/** /**
* Draw a line chart using Highcharts library * Draw a line chart using Highcharts library
* @param {Array} formattedObsArraysForLineChart An array made up of formatted observation array(s) suitable for use in a line chart * @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 * @param {Object} extractedFormattedDatastreamPropertiesArr An object that contains arrays of formatted Datastream properties
* @returns {undefined} undefined * @returns {undefined} undefined
*/ */
const drawLineChartHighcharts = function ( const drawLineChartHighcharts = function (
formattedObsArraysForLineChart, formattedObsArraysForLineChart,
formattedDatastreamMetadataArr extractedFormattedDatastreamProperties
) { ) {
// Arrays of datastream properties // Arrays of datastream properties
const { const {
datastreamNamesArr, datastreamNamesArr,
phenomenonNamesArr, phenomenonNamesArr,
unitOfMeasurementSymbolsArr, unitOfMeasurementSymbolsArr,
} = extractPropertiesFromDatastreamMetadata(formattedDatastreamMetadataArr); } = extractedFormattedDatastreamProperties;
// Create the array of series options object(s) // Create the array of series options object(s)
const seriesOptionsArr = createSeriesOptionsForLineChart( const seriesOptionsArr = createSeriesOptionsForLineChart(
......
...@@ -183,11 +183,7 @@ const createCombinedObservationValues = function (obsArrayOne, obsArrayTwo) { ...@@ -183,11 +183,7 @@ const createCombinedObservationValues = function (obsArrayOne, obsArrayTwo) {
const obsValuesTwo = obsArrayTwo.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 // Since the arrays are of equal length, we need only use one of the arrays for looping
const obsValuesOnePlusTwo = obsValuesOne.map((obsValOne, i) => { return obsValuesOne.map((obsValOne, i) => [obsValOne, obsValuesTwo[i]]);
return [obsValOne, obsValuesTwo[i]];
});
return obsValuesOnePlusTwo;
}; };
/** /**
...@@ -218,28 +214,29 @@ const formatSensorThingsApiResponseForScatterPlot = function ( ...@@ -218,28 +214,29 @@ const formatSensorThingsApiResponseForScatterPlot = function (
/** /**
* Draw a scatter plot using Highcharts library * Draw a scatter plot using Highcharts library
* @param {Array} formattedObsArrayForSeriesOnePlusSeriesTwo Response from SensorThings API formatted for use in a scatter plot * @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} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties
* @param {Object} formattedDatastreamMetadataSeriesTwo Object containing Datastream metadata for the second chart series
* @returns {undefined} * @returns {undefined}
*/ */
const drawScatterPlotHighcharts = function ( const drawScatterPlotHighcharts = function (
formattedObsArrayForSeriesOnePlusSeriesTwo, formattedObsArrayForSeriesOnePlusSeriesTwo,
formattedDatastreamMetadataSeriesOne, extractedFormattedDatastreamProperties
formattedDatastreamMetadataSeriesTwo
) { ) {
// Arrays of datastream properties
const { const {
datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_1, datastreamDescriptionsArr,
datastreamName: DATASTREAM_NAME_SERIES_1, datastreamNamesArr,
phenomenonName: PHENOMENON_NAME_SERIES_1, phenomenonNamesArr,
unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_1, unitOfMeasurementSymbolsArr,
} = formattedDatastreamMetadataSeriesOne; } = extractedFormattedDatastreamProperties;
const { const [DATASTREAM_DESCRIPTION_SERIES_1, DATASTREAM_DESCRIPTION_SERIES_2] =
datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_2, datastreamDescriptionsArr;
datastreamName: DATASTREAM_NAME_SERIES_2, const [DATASTREAM_NAME_SERIES_1, DATASTREAM_NAME_SERIES_2] =
phenomenonName: PHENOMENON_NAME_SERIES_2, datastreamNamesArr;
unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_2, const [PHENOMENON_NAME_SERIES_1, PHENOMENON_NAME_SERIES_2] =
} = formattedDatastreamMetadataSeriesTwo; phenomenonNamesArr;
const [PHENOMENON_SYMBOL_SERIES_1, PHENOMENON_SYMBOL_SERIES_2] =
unitOfMeasurementSymbolsArr;
// Order of axes // Order of axes
// Y-Axis -- Series 2 // Y-Axis -- Series 2
......
...@@ -233,6 +233,39 @@ const formatDatastreamMetadataForChart = function (datastreamMetadata) { ...@@ -233,6 +233,39 @@ const formatDatastreamMetadataForChart = function (datastreamMetadata) {
}; };
}; };
/**
* Extract the properties that make up the formatted datastream metadata object(s)
* @param {Array} formattedDatastreamsMetadataArr An array of formatted metadata object(s) from one or more datastreams
* @returns {Object} An object that contains array(s) of formatted datastream metadata properties
*/
const extractPropertiesFromFormattedDatastreamMetadata = function (
formattedDatastreamsMetadataArr
) {
// Create arrays from the properties of the formatted datastream metadata
const datastreamDescriptionsArr = formattedDatastreamsMetadataArr.map(
(datastreamMetadata) => datastreamMetadata.datastreamDescription
);
const datastreamNamesArr = formattedDatastreamsMetadataArr.map(
(datastreamMetadata) => datastreamMetadata.datastreamName
);
const phenomenonNamesArr = formattedDatastreamsMetadataArr.map(
(datastreamMetadata) => datastreamMetadata.phenomenonName
);
const unitOfMeasurementSymbolsArr = formattedDatastreamsMetadataArr.map(
(datastreamMetadata) => datastreamMetadata.unitOfMeasurementSymbol
);
return {
datastreamDescriptionsArr,
datastreamNamesArr,
phenomenonNamesArr,
unitOfMeasurementSymbolsArr,
};
};
/** /**
* Traverses all the pages that make up the response from a SensorThingsAPI instance. The link to the next page, if present, is denoted by the presence of a "@iot.nextLink" property in the response object. This function concatenates all the values so that the complete results are returned in one array. * Traverses all the pages that make up the response from a SensorThingsAPI instance. The link to the next page, if present, is denoted by the presence of a "@iot.nextLink" property in the response object. This function concatenates all the values so that the complete results are returned in one array.
* @async * @async
...@@ -332,18 +365,18 @@ const getObservationsFromMultipleDatastreams = async function ( ...@@ -332,18 +365,18 @@ const getObservationsFromMultipleDatastreams = async function (
* Retrieve the metadata from a single Datastream or multiple Datastreams and the Observations corresponding to the Datastream(s) * 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 {String} baseUrl Base URL of the STA server
* @param {Object} urlParamObj The URL parameters to be sent together with the GET request * @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 * @param {Array} bldgSensorSamplingRateNestedArr 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 * @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 getMetadataPlusObservationsFromSingleOrMultipleDatastreams = const getMetadataPlusObservationsFromSingleOrMultipleDatastreams =
async function (baseUrl, urlParamObj, bldgSensorSamplingRateArr) { async function (baseUrl, urlParamObj, bldgSensorSamplingRateNestedArr) {
try { try {
if (!bldgSensorSamplingRateArr) return; if (!bldgSensorSamplingRateNestedArr) return;
// Datastreams IDs // Datastreams IDs
const datastreamsIdsArr = bldgSensorSamplingRateArr.map( const datastreamsIdsArr = bldgSensorSamplingRateNestedArr.map(
(bldgSensorSamplingRate) => (bldgSensorSamplingRateArr) =>
getDatastreamIdFromBuildingNumber(...bldgSensorSamplingRate) getDatastreamIdFromBuildingNumber(...bldgSensorSamplingRateArr)
); );
// Observations URLs // Observations URLs
...@@ -394,7 +427,7 @@ const calculateVorlaufMinusRuecklaufTemperature = async function ( ...@@ -394,7 +427,7 @@ const calculateVorlaufMinusRuecklaufTemperature = async function (
samplingRate samplingRate
) { ) {
try { try {
const bldgSensorSamplingRateArr = [ const bldgSensorSamplingRateNestedArr = [
[buildingId, "vl", samplingRate], [buildingId, "vl", samplingRate],
[buildingId, "rl", samplingRate], [buildingId, "rl", samplingRate],
]; ];
...@@ -406,23 +439,31 @@ const calculateVorlaufMinusRuecklaufTemperature = async function ( ...@@ -406,23 +439,31 @@ const calculateVorlaufMinusRuecklaufTemperature = async function (
await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
baseUrl, baseUrl,
urlParams, urlParams,
bldgSensorSamplingRateArr bldgSensorSamplingRateNestedArr
); );
// Extract Vorlauf temperature, Ruecklauf temperature and metadata // Extract Vorlauf temperature, Ruecklauf temperature and metadata
const [[vorlaufTemp, ruecklaufTemp], [metadataVorlauf, metadataRuecklauf]] = const [
observationsPlusMetadata; [vorlaufTemperatureObsArr, ruecklaufTemperatureObsArr],
[metadataVorlauf, metadataRuecklauf],
] = observationsPlusMetadata;
// Extract the temperature values // Extract the temperature values
const vorlaufTempValues = vorlaufTemp.map((obs) => obs[1]); const vorlaufTemperatureValues = vorlaufTemperatureObsArr.map(
const ruecklaufTempValues = ruecklaufTemp.map((obs) => obs[1]); (vlTempObs) => vlTempObs[1]
);
const ruecklaufTemperatureValues = ruecklaufTemperatureObsArr.map(
(rlTempObs) => rlTempObs[1]
);
// The arrays have equal length, we need only use one of them for looping // The arrays have equal length, we need only use one of them for looping
// Resulting array contains the following pairs (timestamp + dT) // Resulting array contains the following pairs (timestamp + dT)
const vorlaufMinusRuecklaufTemp = vorlaufTemp.map((obs, i) => [ const vorlaufMinusRuecklaufTemperatureObs = vorlaufTemperatureObsArr.map(
obs[0], (vlTempObs, i) => [
vorlaufTempValues[i] - ruecklaufTempValues[i], vlTempObs[0], // timestamp
]); vorlaufTemperatureValues[i] - ruecklaufTemperatureValues[i],
]
);
// From Vorlauf metadata, extract `name` and `unitOfMeasurement` // From Vorlauf metadata, extract `name` and `unitOfMeasurement`
const { const {
...@@ -459,7 +500,7 @@ const calculateVorlaufMinusRuecklaufTemperature = async function ( ...@@ -459,7 +500,7 @@ const calculateVorlaufMinusRuecklaufTemperature = async function (
const unitOfMeasurement = unitOfMeasurementVorlauf; const unitOfMeasurement = unitOfMeasurementVorlauf;
return [ return [
vorlaufMinusRuecklaufTemp, vorlaufMinusRuecklaufTemperatureObs,
{ {
description, description,
name, name,
...@@ -473,6 +514,7 @@ const calculateVorlaufMinusRuecklaufTemperature = async function ( ...@@ -473,6 +514,7 @@ const calculateVorlaufMinusRuecklaufTemperature = async function (
export { export {
formatDatastreamMetadataForChart, formatDatastreamMetadataForChart,
extractPropertiesFromFormattedDatastreamMetadata,
getMetadataPlusObservationsFromSingleOrMultipleDatastreams, getMetadataPlusObservationsFromSingleOrMultipleDatastreams,
calculateVorlaufMinusRuecklaufTemperature, calculateVorlaufMinusRuecklaufTemperature,
}; };
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment