Commit 7196482d authored by Pithon Kabiro's avatar Pithon Kabiro
Browse files

Merge branch 'wip_chart-column' into 'master'

Add functionality to draw multi-series column chart

See merge request !7
parents 1c71824b 786a5e18
...@@ -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,
}; };
Supports Markdown
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