Commit 233f7b2c authored by Pithon Kabiro's avatar Pithon Kabiro
Browse files

Draw multiple series on same scatter plot

parent 4ae783c2
Showing with 978 additions and 643 deletions
+978 -643
...@@ -34,9 +34,13 @@ import { ...@@ -34,9 +34,13 @@ import {
import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs"; import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs";
import { import {
calculateSumOfObservationValuesWithinInterval, extractObservationsWithinDatesInterval,
extractUniqueCalendarDatesFromTimestamp, extractUniqueCalendarDatesFromTimestamp,
extractUniqueCalendarMonthsFromCalendarDates, extractUniqueCalendarMonthsFromCalendarDates,
} from "./src_modules/aggregateHelpers.mjs";
import {
calculateSumOfObservationValuesWithinInterval,
calculateAverageOfObservationValuesWithinInterval, calculateAverageOfObservationValuesWithinInterval,
} from "./src_modules/aggregate.mjs"; } from "./src_modules/aggregate.mjs";
...@@ -44,388 +48,477 @@ import { ...@@ -44,388 +48,477 @@ 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 [observationsTemperatureDiff225Arr, metadataTemperatureDiff225Arr] = try {
await calculateVorlaufMinusRuecklaufTemperature( const [observationsTemperatureDiff225Arr, metadataTemperatureDiff225Arr] =
BASE_URL, await calculateVorlaufMinusRuecklaufTemperature(
QUERY_PARAMS_COMBINED, BASE_URL,
"225", QUERY_PARAMS_COMBINED,
"60min" "225",
); "60min"
);
// We want to have nested arrays, so as to mimick the nested responses we get from fetching observations + metadata
const observationsTemperatureDiff225NestedArr = [ // We want to have nested arrays, so as to mimick the nested responses we get from fetching observations + metadata
observationsTemperatureDiff225Arr, const observationsTemperatureDiff225NestedArr = [
]; observationsTemperatureDiff225Arr,
];
const metadataTemperatureDiff225NestedArr = [metadataTemperatureDiff225Arr];
const metadataTemperatureDiff225NestedArr = [metadataTemperatureDiff225Arr];
// Format the observations
const formattedTempDiff225NestedArr = // Format the observations
observationsTemperatureDiff225NestedArr.map((obsArr) => const formattedTempDiff225NestedArr =
formatSensorThingsApiResponseForHeatMap(obsArr) observationsTemperatureDiff225NestedArr.map((obsArr) =>
); formatSensorThingsApiResponseForHeatMap(obsArr)
);
// Format the metadata
const formattedTempDiff225MetadataNestedArr = // Format the metadata
metadataTemperatureDiff225NestedArr.map((metadataObj) => const formattedTempDiff225MetadataNestedArr =
formatDatastreamMetadataForChart(metadataObj) metadataTemperatureDiff225NestedArr.map((metadataObj) =>
); formatDatastreamMetadataForChart(metadataObj)
);
// Extract the formatted metadata properties
const extractedFormattedTempDiff225Properties = // Extract the formatted metadata properties
extractPropertiesFromFormattedDatastreamMetadata( const extractedFormattedTempDiff225Properties =
formattedTempDiff225MetadataNestedArr extractPropertiesFromFormattedDatastreamMetadata(
formattedTempDiff225MetadataNestedArr
);
// First need to extract the formatted observations from the nested array
// Heatmap only needs one set of formatted observation values
drawHeatMapHighcharts(
...formattedTempDiff225NestedArr,
extractedFormattedTempDiff225Properties
); );
} catch (err) {
// First need to extract the formatted observations from the nested array console.error(err);
// Heatmap only needs one set of formatted observation values }
drawHeatMapHighcharts(
...formattedTempDiff225NestedArr,
extractedFormattedTempDiff225Properties
);
}; };
/** /**
* Test drawing of scatter plot chart * Test drawing of scatter plot chart
*/ */
const drawScatterPlotHCTest2 = async function () { const drawScatterPlotHCTest2 = async function () {
const sensorsOfInterestNestedArr = [ try {
["225", "vl", "60min"], const sensorsOfInterestNestedArr = [
// ["125", "rl", "60min"], ["weather_station_521", "outside_temp", "60min"],
["weather_station_521", "outside_temp", "60min"], ["225", "vl", "60min"],
]; ["125", "rl", "60min"],
];
const observationsPlusMetadata =
await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( const observationsPlusMetadata =
BASE_URL, await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
QUERY_PARAMS_COMBINED, BASE_URL,
sensorsOfInterestNestedArr QUERY_PARAMS_COMBINED,
sensorsOfInterestNestedArr
);
// Extract the combined arrays for observations and metadata
const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata;
// Extract values for x-axis and y-axis
// x-axis values are first element of nested observations array
const [obsXAxisArr] = observationsNestedArr.slice(0, 1);
// y-axis values are rest of elements of nested observations array
const obsYAxisNestedArr = observationsNestedArr.slice(1);
// Create formatted array(s) for observations
const formattedObservationsArr = obsYAxisNestedArr.map((obsYAxisArr) =>
formatSensorThingsApiResponseForScatterPlot(obsXAxisArr, obsYAxisArr)
); );
// Extract the combined arrays for observations and metadata // Create formatted array(s) for metadata
const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata; const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
formatDatastreamMetadataForChart(metadataObj)
// Create formatted array(s) for observations );
// This function expects two arguments, these are unpacked using the spread operator
const formattedObservationsArr = formatSensorThingsApiResponseForScatterPlot(
...observationsNestedArr
);
// Create formatted array(s) for metadata // Extract the formatted metadata properties
const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => const extractedFormattedDatastreamProperties =
formatDatastreamMetadataForChart(metadataObj) extractPropertiesFromFormattedDatastreamMetadata(
); formattedMetadataNestedArr
);
// Extract the formatted metadata properties drawScatterPlotHighcharts(
const extractedFormattedDatastreamProperties = formattedObservationsArr,
extractPropertiesFromFormattedDatastreamMetadata( extractedFormattedDatastreamProperties
formattedMetadataNestedArr
); );
} catch (err) {
drawScatterPlotHighcharts( console.error(err);
formattedObservationsArr, }
extractedFormattedDatastreamProperties
);
}; };
/** /**
* 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 sensorsOfInterestNestedArr = [ try {
["225", "vl", "60min"], const sensorsOfInterestNestedArr = [
["125", "rl", "60min"], ["225", "vl", "60min"],
["weather_station_521", "outside_temp", "60min"], ["125", "rl", "60min"],
]; ["weather_station_521", "outside_temp", "60min"],
];
const observationsPlusMetadataArr =
await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( const observationsPlusMetadataArr =
BASE_URL, await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
QUERY_PARAMS_COMBINED, BASE_URL,
sensorsOfInterestNestedArr QUERY_PARAMS_COMBINED,
sensorsOfInterestNestedArr
);
// Extract the observations and metadata arrays of arrays
const [observationsNestedArr, metadataNestedArr] =
observationsPlusMetadataArr;
// Format the observations
const formattedObservationsNestedArr = observationsNestedArr.map(
(observationsArr) =>
formatSensorThingsApiResponseForLineChart(observationsArr)
); );
// Extract the observations and metadata arrays of arrays // Format the metadata
const [observationsNestedArr, metadataNestedArr] = const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) =>
observationsPlusMetadataArr; formatDatastreamMetadataForChart(metadataArr)
// Format the observations
const formattedObservationsNestedArr = observationsNestedArr.map(
(observationsArr) =>
formatSensorThingsApiResponseForLineChart(observationsArr)
);
// Format the metadata
const formattedMetadataNestedArr = metadataNestedArr.map((metadataArr) =>
formatDatastreamMetadataForChart(metadataArr)
);
// Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr
); );
drawLineChartHighcharts( // Extract the formatted metadata properties
formattedObservationsNestedArr, const extractedFormattedDatastreamProperties =
extractedFormattedDatastreamProperties extractPropertiesFromFormattedDatastreamMetadata(
); formattedMetadataNestedArr
);
drawLineChartHighcharts(
formattedObservationsNestedArr,
extractedFormattedDatastreamProperties
);
} catch (err) {
console.error(err);
}
}; };
/** /**
* Test drawing of column chart using aggregation / sum result - monthly * Test drawing of column chart using aggregation / sum result - monthly
*/ */
const drawColumnChartMonthlySumTest = async function () { const drawColumnChartMonthlySumTest = async function () {
const sensorsOfInterestNestedArr = [ try {
["125", "vl", "60min"], const sensorsOfInterestNestedArr = [
["225", "vl", "60min"], ["125", "vl", "60min"],
]; ["225", "vl", "60min"],
];
const observationsPlusMetadata =
await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( const observationsPlusMetadata =
BASE_URL, await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
QUERY_PARAMS_COMBINED, BASE_URL,
sensorsOfInterestNestedArr QUERY_PARAMS_COMBINED,
sensorsOfInterestNestedArr
);
// Extract the observations and metadata for each sensor
// Array elements in same order as input array
const [obsNestedArr, metadataNestedArr] = observationsPlusMetadata;
// User-specified start date and end date
const startDate = "2020-02-01";
const endDate = "2020-05-31";
// Extract observations within the user-specified start and end date
const observationsNestedArr = obsNestedArr.map((obsArr) =>
extractObservationsWithinDatesInterval(
obsArr,
"60min",
startDate,
endDate
)
); );
// Extract the observations and metadata for each sensor // Unique calendar dates
// Array elements in same order as input array const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata; (observationsArr) =>
extractUniqueCalendarDatesFromTimestamp(observationsArr)
// Unique calendar dates
const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
(observationsArr) =>
extractUniqueCalendarDatesFromTimestamp(observationsArr)
);
// Unique calendar months
const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
(uniqueCalendarDatesArr) =>
extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
);
// Calculate sum of values of observations - monthly
const observationsSumMonthlyNestedArr =
calculateSumOfObservationValuesWithinInterval(
observationsNestedArr,
"60min",
uniqueCalendarMonthsNestedArr,
"monthly"
); );
// Format the observations // Unique calendar months
const formattedObservationsSumMonthlyNestedArr = const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
observationsSumMonthlyNestedArr.map((obsSumMonthlyArr, i) => (uniqueCalendarDatesArr) =>
formatAggregationResultForColumnChart( extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
uniqueCalendarMonthsNestedArr[i],
obsSumMonthlyArr
)
); );
// Format the metadata // Calculate sum of values of observations - monthly
const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => const observationsSumMonthlyNestedArr =
formatDatastreamMetadataForChart(metadataObj) calculateSumOfObservationValuesWithinInterval(
); observationsNestedArr,
"60min",
// Extract the formatted metadata properties uniqueCalendarMonthsNestedArr,
const extractedFormattedDatastreamProperties = "monthly"
extractPropertiesFromFormattedDatastreamMetadata( );
formattedMetadataNestedArr
// Format the observations
const formattedObservationsSumMonthlyNestedArr =
observationsSumMonthlyNestedArr.map((obsSumMonthlyArr, i) =>
formatAggregationResultForColumnChart(
uniqueCalendarMonthsNestedArr[i],
obsSumMonthlyArr
)
);
// Format the metadata
const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
formatDatastreamMetadataForChart(metadataObj)
); );
drawColumnChartHighcharts( // Extract the formatted metadata properties
formattedObservationsSumMonthlyNestedArr, const extractedFormattedDatastreamProperties =
extractedFormattedDatastreamProperties extractPropertiesFromFormattedDatastreamMetadata(
); formattedMetadataNestedArr
);
drawColumnChartHighcharts(
formattedObservationsSumMonthlyNestedArr,
extractedFormattedDatastreamProperties
);
} catch (err) {
console.error(err);
}
}; };
/** /**
* Test drawing of column chart using aggregation / sum result - daily * Test drawing of column chart using aggregation / sum result - daily
*/ */
const drawColumnChartDailySumTest = async function () { const drawColumnChartDailySumTest = async function () {
const sensorsOfInterestNestedArr = [ try {
["125", "vl", "60min"], const sensorsOfInterestNestedArr = [
["225", "vl", "60min"], ["125", "vl", "60min"],
]; ["225", "vl", "60min"],
];
const observationsPlusMetadata =
await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( const observationsPlusMetadata =
BASE_URL, await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
QUERY_PARAMS_COMBINED, BASE_URL,
sensorsOfInterestNestedArr QUERY_PARAMS_COMBINED,
sensorsOfInterestNestedArr
);
// Extract the observations and metadata for each sensor
// Array elements in same order as input array
const [obsNestedArr, metadataNestedArr] = observationsPlusMetadata;
// User-specified start date and end date
const startDate = "2020-02-01";
const endDate = "2020-05-31";
// Extract observations within the user-specified start and end date
const observationsNestedArr = obsNestedArr.map((obsArr) =>
extractObservationsWithinDatesInterval(
obsArr,
"60min",
startDate,
endDate
)
); );
// Extract the observations and metadata for each sensor // Unique calendar dates
// Array elements in same order as input array const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata; (observationsArr) =>
extractUniqueCalendarDatesFromTimestamp(observationsArr)
// Unique calendar dates
const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
(observationsArr) =>
extractUniqueCalendarDatesFromTimestamp(observationsArr)
);
// Calculate sum of values of observations - daily
const observationsSumDailyNestedArr =
calculateSumOfObservationValuesWithinInterval(
observationsNestedArr,
"60min",
uniqueCalendarDatesNestedArr,
"daily"
); );
// Format the observations - daily // Calculate sum of values of observations - daily
const formattedObservationsSumDailyNestedArr = const observationsSumDailyNestedArr =
observationsSumDailyNestedArr.map((obsSumDailyArr, i) => calculateSumOfObservationValuesWithinInterval(
formatAggregationResultForColumnChart( observationsNestedArr,
uniqueCalendarDatesNestedArr[i], "60min",
obsSumDailyArr uniqueCalendarDatesNestedArr,
) "daily"
);
// Format the observations - daily
const formattedObservationsSumDailyNestedArr =
observationsSumDailyNestedArr.map((obsSumDailyArr, i) =>
formatAggregationResultForColumnChart(
uniqueCalendarDatesNestedArr[i],
obsSumDailyArr
)
);
// Format the metadata
const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
formatDatastreamMetadataForChart(metadataObj)
); );
// Format the metadata // Extract the formatted metadata properties
const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => const extractedFormattedDatastreamProperties =
formatDatastreamMetadataForChart(metadataObj) extractPropertiesFromFormattedDatastreamMetadata(
); formattedMetadataNestedArr
);
// Extract the formatted metadata properties drawColumnChartHighcharts(
const extractedFormattedDatastreamProperties = formattedObservationsSumDailyNestedArr,
extractPropertiesFromFormattedDatastreamMetadata( extractedFormattedDatastreamProperties
formattedMetadataNestedArr
); );
} catch (err) {
drawColumnChartHighcharts( console.error(err);
formattedObservationsSumDailyNestedArr, }
extractedFormattedDatastreamProperties
);
}; };
/** /**
* Test drawing of line chart using aggregation / average result - monthly * Test drawing of line chart using aggregation / average result - monthly
*/ */
const drawLineChartMonthlyAverageTest = async function () { const drawLineChartMonthlyAverageTest = async function () {
const sensorsOfInterestNestedArr = [ try {
["125", "vl", "60min"], const sensorsOfInterestNestedArr = [
["225", "vl", "60min"], ["125", "vl", "60min"],
]; ["225", "vl", "60min"],
];
const observationsPlusMetadata =
await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( const observationsPlusMetadata =
BASE_URL, await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
QUERY_PARAMS_COMBINED, BASE_URL,
sensorsOfInterestNestedArr QUERY_PARAMS_COMBINED,
sensorsOfInterestNestedArr
);
// Extract the observations and metadata for each sensor
// Array elements in same order as input array
const [obsNestedArr, metadataNestedArr] = observationsPlusMetadata;
// User-specified start date and end date
const startDate = "2020-02-01";
const endDate = "2020-05-31";
// Extract observations within the user-specified start and end date
const observationsNestedArr = obsNestedArr.map((obsArr) =>
extractObservationsWithinDatesInterval(
obsArr,
"60min",
startDate,
endDate
)
); );
// Extract the observations and metadata for each sensor // Unique calendar dates
// Array elements in same order as input array const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata; (observationsArr) =>
extractUniqueCalendarDatesFromTimestamp(observationsArr)
// Unique calendar dates
const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
(observationsArr) =>
extractUniqueCalendarDatesFromTimestamp(observationsArr)
);
// Unique calendar months
const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
(uniqueCalendarDatesArr) =>
extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
);
// Calculate average of values of observations - monthly
const observationsAverageMonthlyNestedArr =
calculateAverageOfObservationValuesWithinInterval(
observationsNestedArr,
"60min",
uniqueCalendarMonthsNestedArr,
"monthly"
); );
// Format the observations // Unique calendar months
const formattedObservationsAverageMonthlyNestedArr = const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
observationsAverageMonthlyNestedArr.map((obsAverageMonthlyArr, i) => (uniqueCalendarDatesArr) =>
formatAggregationResultForColumnChart( extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
uniqueCalendarMonthsNestedArr[i],
obsAverageMonthlyArr
)
); );
// Format the metadata // Calculate average of values of observations - monthly
const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => const observationsAverageMonthlyNestedArr =
formatDatastreamMetadataForChart(metadataObj) calculateAverageOfObservationValuesWithinInterval(
); observationsNestedArr,
"60min",
// Extract the formatted metadata properties uniqueCalendarMonthsNestedArr,
const extractedFormattedDatastreamProperties = "monthly"
extractPropertiesFromFormattedDatastreamMetadata( );
formattedMetadataNestedArr
// Format the observations
const formattedObservationsAverageMonthlyNestedArr =
observationsAverageMonthlyNestedArr.map((obsAverageMonthlyArr, i) =>
formatAggregationResultForColumnChart(
uniqueCalendarMonthsNestedArr[i],
obsAverageMonthlyArr
)
);
// Format the metadata
const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
formatDatastreamMetadataForChart(metadataObj)
); );
drawLineChartHighcharts( // Extract the formatted metadata properties
formattedObservationsAverageMonthlyNestedArr, const extractedFormattedDatastreamProperties =
extractedFormattedDatastreamProperties extractPropertiesFromFormattedDatastreamMetadata(
); formattedMetadataNestedArr
);
drawLineChartHighcharts(
formattedObservationsAverageMonthlyNestedArr,
extractedFormattedDatastreamProperties
);
} catch (err) {
console.error(err);
}
}; };
/** /**
* Test drawing of line chart using aggregation / average result - daily * Test drawing of line chart using aggregation / average result - daily
*/ */
const drawLineChartDailyAverageTest = async function () { const drawLineChartDailyAverageTest = async function () {
const sensorsOfInterestNestedArr = [ try {
["125", "vl", "60min"], const sensorsOfInterestNestedArr = [
["225", "vl", "60min"], ["125", "vl", "60min"],
]; ["225", "vl", "60min"],
];
const observationsPlusMetadata =
await getMetadataPlusObservationsFromSingleOrMultipleDatastreams( const observationsPlusMetadata =
BASE_URL, await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
QUERY_PARAMS_COMBINED, BASE_URL,
sensorsOfInterestNestedArr QUERY_PARAMS_COMBINED,
sensorsOfInterestNestedArr
);
// Extract the observations and metadata for each sensor
// Array elements in same order as input array
const [obsNestedArr, metadataNestedArr] = observationsPlusMetadata;
// User-specified start date and end date
const startDate = "2020-02-01";
const endDate = "2020-05-31";
// Extract observations within the user-specified start and end date
const observationsNestedArr = obsNestedArr.map((obsArr) =>
extractObservationsWithinDatesInterval(
obsArr,
"60min",
startDate,
endDate
)
); );
// Extract the observations and metadata for each sensor // Unique calendar dates
// Array elements in same order as input array const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
const [observationsNestedArr, metadataNestedArr] = observationsPlusMetadata; (observationsArr) =>
extractUniqueCalendarDatesFromTimestamp(observationsArr)
// Unique calendar dates
const uniqueCalendarDatesNestedArr = observationsNestedArr.map(
(observationsArr) =>
extractUniqueCalendarDatesFromTimestamp(observationsArr)
);
// Calculate average of values of observations - daily
const observationsAverageDailyNestedArr =
calculateAverageOfObservationValuesWithinInterval(
observationsNestedArr,
"60min",
uniqueCalendarDatesNestedArr,
"daily"
); );
// Format the observations - daily // Calculate average of values of observations - daily
const formattedObservationsAverageDailyNestedArr = const observationsAverageDailyNestedArr =
observationsAverageDailyNestedArr.map((obsAverageDailyArr, i) => calculateAverageOfObservationValuesWithinInterval(
formatAggregationResultForColumnChart( observationsNestedArr,
uniqueCalendarDatesNestedArr[i], "60min",
obsAverageDailyArr uniqueCalendarDatesNestedArr,
) "daily"
);
// Format the observations - daily
const formattedObservationsAverageDailyNestedArr =
observationsAverageDailyNestedArr.map((obsAverageDailyArr, i) =>
formatAggregationResultForColumnChart(
uniqueCalendarDatesNestedArr[i],
obsAverageDailyArr
)
);
// Format the metadata
const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) =>
formatDatastreamMetadataForChart(metadataObj)
); );
// Format the metadata // Extract the formatted metadata properties
const formattedMetadataNestedArr = metadataNestedArr.map((metadataObj) => const extractedFormattedDatastreamProperties =
formatDatastreamMetadataForChart(metadataObj) extractPropertiesFromFormattedDatastreamMetadata(
); formattedMetadataNestedArr
);
// Extract the formatted metadata properties drawLineChartHighcharts(
const extractedFormattedDatastreamProperties = formattedObservationsAverageDailyNestedArr,
extractPropertiesFromFormattedDatastreamMetadata( extractedFormattedDatastreamProperties
formattedMetadataNestedArr
); );
} catch (err) {
drawLineChartHighcharts( console.error(err);
formattedObservationsAverageDailyNestedArr, }
extractedFormattedDatastreamProperties
);
}; };
// drawScatterPlotHCTest2(); // drawScatterPlotHCTest2();
......
"use strict"; "use strict";
/** import {
* Create 24-hour time strings for a time interval delimited by a start time and an end time. It is assumed that the start time is at "00:00:00" and the end time is at "23:45:00" (when the sampling rate of observations is 15 min) or "23:00:00" (when the sampling rate of observations is 60 min) extractObservationValuesWithinDatesInterval,
* @param {String} phenomenonSamplingRate The sampling rate of the phenomenon of interest represented as a string, e.g. "15min", "60min" extractObservationValuesWithinMonthInterval,
* @returns {Array} An array of two 24-hour strings representing the start time and end time } from "./aggregateHelpers.mjs";
*/
const createTimeStringsForInterval = function (phenomenonSamplingRate) {
const fifteenMinutes = "15min";
const sixtyMinutes = "60min";
const startTime = "00:00:00";
const endTimeFifteenMinutes = "23:45:00";
const endTimeSixtyMinutes = "23:00:00";
if (
phenomenonSamplingRate !== fifteenMinutes &&
phenomenonSamplingRate !== sixtyMinutes
)
throw new Error(
`Check that the provided phenomenon sampling rate string is in this format: "15min" or "60min"`
);
// 15 min sampling rate
if (phenomenonSamplingRate === fifteenMinutes) {
return [startTime, endTimeFifteenMinutes];
}
// 60 min sampling rate
if (phenomenonSamplingRate === sixtyMinutes) {
return [startTime, endTimeSixtyMinutes];
}
};
/**
* Create an ISO 8601 date and time string
* @param {String} inputCalendarDate Calendar date string in "YYYY-MM-DD" format
* @param {String} inputTwentyFourHourTime 24-hour time string in "hh:mm:ss" format
* @returns {String} An ISO 8601 date and time string
*/
const createIso8601DateTimeString = function (
inputCalendarDate,
inputTwentyFourHourTime
) {
return `${inputCalendarDate}T${inputTwentyFourHourTime}.000Z`;
};
/**
* Check whether a year is a leap year or not
* @param {Number} year Integer representing the year
* @returns {Boolean} true if leap year, false if not
*/
const checkIfLeapYear = function (year) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
};
/**
* Calculate the index of a timestamp in an array of timestamps
* @param {Array} inputTimestampArr An array of timestamps, extracted from an array of observations
* @param {String} timestampOfInterest A string representing the timestamp of interest in ISO 8601 format
* @returns {Number} An integer representing the index of the timestamp of interest in the array of timestamps
*/
const getIndexOfTimestamp = function (inputTimestampArr, timestampOfInterest) {
const timestampIndex = inputTimestampArr.findIndex(
(timestamp) => timestamp === timestampOfInterest
);
// If the timestamp does not exist in the timestamp array
if (timestampIndex === -1)
throw new Error(
"A start or end timestamp could not be found in the timestamp array"
);
// If the timestamp exists in the timestamp array
return timestampIndex;
};
/**
* Calculate the indexes of the start and end timestamps
* @param {Array} obsTimestampArr An array of observations timestamps
* @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
* @param {String} startDate A 24-hour date string representing the start date
* @param {String} endDate A 24-hour date string representing the end date
* @returns {Array} A 1*2 array tht contains integers representing the start index and end index respectively
*/
const calculateIndexStartEndTimestamp = function (
obsTimestampArr,
samplingRate,
startDate,
endDate
) {
// Create and extract 24-hour strings for the start and end of interval
const [startTimeString, endTimeString] =
createTimeStringsForInterval(samplingRate);
// Create ISO 8601 strings for the start and end of interval
const startIso8601DateTimeString = createIso8601DateTimeString(
startDate,
startTimeString
);
const endIso8601DateTimeString = createIso8601DateTimeString(
endDate,
endTimeString
);
// Calculate the indexes of the timestamps for the start and end of interval
const indexStartTimestamp = getIndexOfTimestamp(
obsTimestampArr,
startIso8601DateTimeString
);
const indexEndTimestamp = getIndexOfTimestamp(
obsTimestampArr,
endIso8601DateTimeString
);
return [indexStartTimestamp, indexEndTimestamp];
};
/**
* Extract the set of observation values that fall within a time interval delimited by a start date and end date. The start date may be the same as the end date.
* @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
* @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
* @param {String} startDate A 24-hour date string representing the start date
* @param {String} endDate A 24-hour date string representing the end date
* @returns {Array} An array of observation values that fall within our time interval
*/
const extractObservationValuesWithinDatesInterval = function (
obsArray,
samplingRate,
startDate,
endDate
) {
// Extract the timestamps and values from the observations
const obsTimestampArr = obsArray.map((obs) => obs[0]);
const obsValuesArr = obsArray.map((obs) => obs[1]);
// Calculate the indexes of the timestamps for the start and end of interval
const [indexStartTimestamp, indexEndTimestamp] =
calculateIndexStartEndTimestamp(
obsTimestampArr,
samplingRate,
startDate,
endDate
);
// Extract the observations that fall within our time interval
return obsValuesArr.slice(indexStartTimestamp, indexEndTimestamp + 1);
};
/** /**
* Calculate the sum of observation values that fall within a time interval delimited by a start date and end date * Calculate the sum of observation values that fall within a time interval delimited by a start date and end date
...@@ -175,82 +33,6 @@ const calculateAverageOfObservationValuesWithinDatesInterval = function ( ...@@ -175,82 +33,6 @@ const calculateAverageOfObservationValuesWithinDatesInterval = function (
); );
}; };
/**
* Extract the year and month digits from a calendar month string
* @param {String} calendarMonthStr Calendar month string in "YYYY-MM" format
* @returns {Array} A 1*2 array tht contains integers representing the year and month respectively
*/
const extractMonthYearDigitsFromCalendarMonthString = function (
calendarMonthStr
) {
// Extract year as integer
const yearNum = parseInt(calendarMonthStr.slice(0, 4), 10);
// Extract month as integer
const monthNum = parseInt(calendarMonthStr.slice(-2), 10);
return [yearNum, monthNum];
};
/**
* Extract the set of observation values that fall within a time interval delimited by the first day and last day of a calendar month
* @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
* @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
* @param {String} calendarMonthStr Calendar month string in "YYYY-MM" format
* @returns {Array} An array of observation values that fall within one calendar month
*/
const extractObservationValuesWithinMonthInterval = function (
obsArray,
samplingRate,
calendarMonthStr
) {
// Extract the year and month digits from the calendar month string
const [yearNum, monthNum] =
extractMonthYearDigitsFromCalendarMonthString(calendarMonthStr);
if (monthNum < 1 || monthNum > 12) return;
// All the months start on the first
const startDateStr = `${calendarMonthStr}-01`;
// February
if (monthNum === 2) {
// Leap year
if (checkIfLeapYear(yearNum))
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-29`
);
// Non-leap year
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-28`
);
}
// Months with 30 days
if (monthNum === 4 || monthNum === 6 || monthNum === 9 || monthNum === 11)
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-30`
);
// Months with 31 days
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-31`
);
};
/** /**
* Calculate the sum of observation values that fall within a time interval delimited by the first day and last day of a calendar month * Calculate the sum of observation values that fall within a time interval delimited by the first day and last day of a calendar month
* @param {Array} obsValuesForMonthIntervalArr An array of observation values that fall within one calendar month * @param {Array} obsValuesForMonthIntervalArr An array of observation values that fall within one calendar month
...@@ -375,46 +157,7 @@ const calculateAverageOfObservationValuesWithinInterval = function ( ...@@ -375,46 +157,7 @@ const calculateAverageOfObservationValuesWithinInterval = function (
} }
}; };
/**
* Extract unique calendar dates from date/time strings in ISO 8601 format
* @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
* @returns {Array} An array of unique calendar date strings in "YYYY-MM-DD" format
*/
const extractUniqueCalendarDatesFromTimestamp = function (obsArray) {
// The timestamp is the first element of the observation array
const timestampArr = obsArray.map((obs) => obs[0]);
// Extract the calendar date string from the timestamp string
const calendarDateArr = timestampArr.map((timestamp) =>
timestamp.slice(0, 10)
);
// Use a set to remove duplicates
const uniqueCalendarDates = new Set(calendarDateArr);
return [...uniqueCalendarDates];
};
/**
* Extract unique calendar months from calendar date strings
* @param {Array} calendarDatesArr An array of unique calendar date strings in "YYYY-MM-DD" format
* @returns {Array} An array of unique calendar month strings in "YYYY-MM" format
*/
const extractUniqueCalendarMonthsFromCalendarDates = function (
calendarDatesArr
) {
// Extract the calendar month strings
const calendarMonthsArr = calendarDatesArr.map((date) => date.slice(0, 7));
// Use a set to remove duplicates
const uniqueCalendarMonths = new Set(calendarMonthsArr);
return [...uniqueCalendarMonths];
};
export { export {
calculateSumOfObservationValuesWithinInterval, calculateSumOfObservationValuesWithinInterval,
extractUniqueCalendarDatesFromTimestamp,
extractUniqueCalendarMonthsFromCalendarDates,
calculateAverageOfObservationValuesWithinInterval, calculateAverageOfObservationValuesWithinInterval,
}; };
"use strict";
/**
* Create 24-hour time strings for a time interval delimited by a start time and an end time. It is assumed that the start time is at "00:00:00" and the end time is at "23:45:00" (when the sampling rate of observations is 15 min) or "23:00:00" (when the sampling rate of observations is 60 min)
* @param {String} phenomenonSamplingRate The sampling rate of the phenomenon of interest represented as a string, e.g. "15min", "60min"
* @returns {Array} An array of two 24-hour strings representing the start time and end time
*/
const createTimeStringsForInterval = function (phenomenonSamplingRate) {
const fifteenMinutes = "15min";
const sixtyMinutes = "60min";
const startTime = "00:00:00";
const endTimeFifteenMinutes = "23:45:00";
const endTimeSixtyMinutes = "23:00:00";
if (
phenomenonSamplingRate !== fifteenMinutes &&
phenomenonSamplingRate !== sixtyMinutes
)
throw new Error(
`Check that the provided phenomenon sampling rate string is in this format: "15min" or "60min"`
);
// 15 min sampling rate
if (phenomenonSamplingRate === fifteenMinutes) {
return [startTime, endTimeFifteenMinutes];
}
// 60 min sampling rate
if (phenomenonSamplingRate === sixtyMinutes) {
return [startTime, endTimeSixtyMinutes];
}
};
/**
* Create an ISO 8601 date and time string
* @param {String} inputCalendarDate Calendar date string in "YYYY-MM-DD" format
* @param {String} inputTwentyFourHourTime 24-hour time string in "hh:mm:ss" format
* @returns {String} An ISO 8601 date and time string
*/
const createIso8601DateTimeString = function (
inputCalendarDate,
inputTwentyFourHourTime
) {
return `${inputCalendarDate}T${inputTwentyFourHourTime}.000Z`;
};
/**
* Check whether a year is a leap year or not
* @param {Number} year Integer representing the year
* @returns {Boolean} true if leap year, false if not
*/
const checkIfLeapYear = function (year) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
};
/**
* Calculate the index of a timestamp in an array of timestamps
* @param {Array} inputTimestampArr An array of timestamps, extracted from an array of observations
* @param {String} timestampOfInterest A string representing the timestamp of interest in ISO 8601 format
* @returns {Number} An integer representing the index of the timestamp of interest in the array of timestamps
*/
const getIndexOfTimestamp = function (inputTimestampArr, timestampOfInterest) {
const timestampIndex = inputTimestampArr.findIndex(
(timestamp) => timestamp === timestampOfInterest
);
// If the timestamp does not exist in the timestamp array
if (timestampIndex === -1)
throw new Error(
"A start or end timestamp could not be found in the timestamp array"
);
// If the timestamp exists in the timestamp array
return timestampIndex;
};
/**
* Calculate the indexes of the start and end timestamps
* @param {Array} obsTimestampArr An array of observations timestamps
* @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
* @param {String} startDate A calendar date string in "YYYY-MM-DD" format representing the start date
* @param {String} endDate A calendar date string in "YYYY-MM-DD" format representing the end date
* @returns {Array} A 1*2 array tht contains integers representing the start index and end index respectively
*/
const calculateIndexStartEndTimestamp = function (
obsTimestampArr,
samplingRate,
startDate,
endDate
) {
// Create and extract 24-hour strings for the start and end of interval
const [startTimeString, endTimeString] =
createTimeStringsForInterval(samplingRate);
// Create ISO 8601 strings for the start and end of interval
const startIso8601DateTimeString = createIso8601DateTimeString(
startDate,
startTimeString
);
const endIso8601DateTimeString = createIso8601DateTimeString(
endDate,
endTimeString
);
// Calculate the indexes of the timestamps for the start and end of interval
const indexStartTimestamp = getIndexOfTimestamp(
obsTimestampArr,
startIso8601DateTimeString
);
const indexEndTimestamp = getIndexOfTimestamp(
obsTimestampArr,
endIso8601DateTimeString
);
return [indexStartTimestamp, indexEndTimestamp];
};
/**
* Extract the set of observations that fall within a time interval delimited by a start date and end date. The start date may be the same as the end date.
* @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
* @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
* @param {String} startDate A calendar date string in "YYYY-MM-DD" format representing the start date
* @param {String} endDate A calendar date string in "YYYY-MM-DD" format representing the end date
* @returns {Array} An array of observations (timestamp + value) that fall within our time interval
*/
const extractObservationsWithinDatesInterval = function (
obsArray,
samplingRate,
startDate,
endDate
) {
// Extract the timestamps from the observations
const obsTimestampArr = obsArray.map((obs) => obs[0]);
// Calculate the indexes of the timestamps for the start and end of interval
const [indexStartTimestamp, indexEndTimestamp] =
calculateIndexStartEndTimestamp(
obsTimestampArr,
samplingRate,
startDate,
endDate
);
// Extract the observations that fall within our time interval
return obsArray.slice(indexStartTimestamp, indexEndTimestamp + 1);
};
/**
* Extract the set of observation values that fall within a time interval delimited by a start date and end date. The start date may be the same as the end date.
* @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
* @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
* @param {String} startDate A calendar date string in "YYYY-MM-DD" format representing the start date
* @param {String} endDate A calendar date string in "YYYY-MM-DD" format representing the end date
* @returns {Array} An array of observation values that fall within our time interval
*/
const extractObservationValuesWithinDatesInterval = function (
obsArray,
samplingRate,
startDate,
endDate
) {
// Extract the timestamps and values from the observations
const obsTimestampArr = obsArray.map((obs) => obs[0]);
const obsValuesArr = obsArray.map((obs) => obs[1]);
// Calculate the indexes of the timestamps for the start and end of interval
const [indexStartTimestamp, indexEndTimestamp] =
calculateIndexStartEndTimestamp(
obsTimestampArr,
samplingRate,
startDate,
endDate
);
// Extract the observation values that fall within our time interval
return obsValuesArr.slice(indexStartTimestamp, indexEndTimestamp + 1);
};
/**
* Extract the year and month digits from a calendar month string
* @param {String} calendarMonthStr Calendar month string in "YYYY-MM" format
* @returns {Array} A 1*2 array tht contains integers representing the year and month respectively
*/
const extractMonthYearDigitsFromCalendarMonthString = function (
calendarMonthStr
) {
// Extract year as integer
const yearNum = parseInt(calendarMonthStr.slice(0, 4), 10);
// Extract month as integer
const monthNum = parseInt(calendarMonthStr.slice(-2), 10);
return [yearNum, monthNum];
};
/**
* Extract the set of observation values that fall within a time interval delimited by the first day and last day of a calendar month
* @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
* @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
* @param {String} calendarMonthStr Calendar month string in "YYYY-MM" format
* @returns {Array} An array of observation values that fall within one calendar month
*/
const extractObservationValuesWithinMonthInterval = function (
obsArray,
samplingRate,
calendarMonthStr
) {
// Extract the year and month digits from the calendar month string
const [yearNum, monthNum] =
extractMonthYearDigitsFromCalendarMonthString(calendarMonthStr);
if (monthNum < 1 || monthNum > 12) return;
// All the months start on the first
const startDateStr = `${calendarMonthStr}-01`;
// February
if (monthNum === 2) {
// Leap year
if (checkIfLeapYear(yearNum))
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-29`
);
// Non-leap year
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-28`
);
}
// Months with 30 days
if (monthNum === 4 || monthNum === 6 || monthNum === 9 || monthNum === 11)
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-30`
);
// Months with 31 days
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-31`
);
};
/**
* Extract unique calendar dates from date/time strings in ISO 8601 format
* @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
* @returns {Array} An array of unique calendar date strings in "YYYY-MM-DD" format
*/
const extractUniqueCalendarDatesFromTimestamp = function (obsArray) {
// The timestamp is the first element of the observation array
const timestampArr = obsArray.map((obs) => obs[0]);
// Extract the calendar date string from the timestamp string
const calendarDateArr = timestampArr.map((timestamp) =>
timestamp.slice(0, 10)
);
// Use a set to remove duplicates
const uniqueCalendarDates = new Set(calendarDateArr);
return [...uniqueCalendarDates];
};
/**
* Extract unique calendar months from calendar date strings
* @param {Array} calendarDatesArr An array of unique calendar date strings in "YYYY-MM-DD" format
* @returns {Array} An array of unique calendar month strings in "YYYY-MM" format
*/
const extractUniqueCalendarMonthsFromCalendarDates = function (
calendarDatesArr
) {
// Extract the calendar month strings
const calendarMonthsArr = calendarDatesArr.map((date) => date.slice(0, 7));
// Use a set to remove duplicates
const uniqueCalendarMonths = new Set(calendarMonthsArr);
return [...uniqueCalendarMonths];
};
export {
extractObservationsWithinDatesInterval,
extractObservationValuesWithinDatesInterval,
extractObservationValuesWithinMonthInterval,
extractUniqueCalendarDatesFromTimestamp,
extractUniqueCalendarMonthsFromCalendarDates,
};
"use strict"; "use strict";
import { chartExportOptions } from "./chartExport.mjs"; import { chartExportOptions } from "./chartHelpers.mjs";
/** /**
* Format a computed aggregation result to make it suitable for a column chart * Format a computed aggregation result to make it suitable for a column chart
...@@ -42,6 +42,13 @@ const createSeriesOptionsForColumnChart = function ( ...@@ -42,6 +42,13 @@ const createSeriesOptionsForColumnChart = function (
// Create an array of seriesOptions objects // Create an array of seriesOptions objects
// Assumes that the observation array of arrays, phenomenon names array and phenomenon symbols array are of equal length // 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 // Use one of the arrays for looping
if (
formattedAggregatedResultForColumnChart.length !== phenomenonNamesArr.length
)
throw new Error(
"The observations array and phenomenon names array have different lengths"
);
return formattedAggregatedResultForColumnChart.map( return formattedAggregatedResultForColumnChart.map(
(formattedAggResArray, i) => { (formattedAggResArray, i) => {
return { return {
...@@ -57,7 +64,7 @@ const createSeriesOptionsForColumnChart = function ( ...@@ -57,7 +64,7 @@ const createSeriesOptionsForColumnChart = function (
* Draw a column chart using Highcharts library * 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 {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 * @param {Object} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties
* @returns {undefined} * @returns {undefined} undefined
*/ */
const drawColumnChartHighcharts = function ( const drawColumnChartHighcharts = function (
formattedAggResultArraysForColumnChart, formattedAggResultArraysForColumnChart,
......
"use strict";
export const chartExportOptions = {
buttons: {
contextButton: {
menuItems: ["downloadPNG", "downloadJPEG", "downloadPDF", "downloadSVG"],
},
},
};
"use strict"; "use strict";
import { chartExportOptions } from "./chartExport.mjs"; import { chartExportOptions } from "./chartHelpers.mjs";
/** /**
* Format the response from SensorThings API to make it suitable for use in a heatmap * Format the response from SensorThings API to make it suitable for use in a heatmap
......
"use strict";
const chartExportOptions = {
buttons: {
contextButton: {
menuItems: ["downloadPNG", "downloadJPEG", "downloadPDF", "downloadSVG"],
},
},
};
/**
* Convert a hexadecimal color code obtained from the Highcharts object (`Highcharts.getOptions().colors`) to its equivalent RGB color code
* @param {String} hexCode Input hex color code
* @returns {String} Output RGB color code
*/
const convertHexColorToRGBColor = function (hexCode) {
const hexToRGBMapping = {
"#7cb5ec": "rgb(124, 181, 236)",
"#434348": "rgb(67, 67, 72)",
"#90ed7d": "rgb(144, 237, 125)",
"#f7a35c": "rgb(247, 163, 92)",
"#8085e9": "rgb(128, 133, 233)",
"#f15c80": "rgb(241, 92, 128)",
"#e4d354": "rgb(228, 211, 84)",
"#2b908f": "rgb(228, 211, 84)",
"#f45b5b": "rgb(244, 91, 91)",
"#91e8e1": "rgb(145, 232, 225)",
};
if (hexToRGBMapping?.[hexCode] === undefined)
throw new Error(
"The provided hex code is not valid or is not supported by this function"
);
// Extract the RGB color elements as a single string
// The individual color elements are separated by commas
return (hexToRGBMapping?.[hexCode]).slice(4, -1);
};
/**
* Concatenates metadata properties into a single string with an ampersand as the delimiter
* @param {Array} metadataPropertiesArr An array of metadata property strings
* @returns {String} A string made up of combined metadata properties delimited by an ampersand
*/
const createCombinedTextDelimitedByAmpersand = function (
metadataPropertiesArr
) {
return metadataPropertiesArr.join(" & ");
};
/**
* Concatenates metadata properties into a single string with a comma as the delimiter
* @param {Array} metadataPropertiesArr An array of metadata property strings
* @returns {String} A string made up of combined metadata properties delimited by a comma
*/
const createCombinedTextDelimitedByComma = function (metadataPropertiesArr) {
return metadataPropertiesArr.join(", ");
};
/**
* Extracts the sampling rate substring from a datastream name string
* @param {Array} datastreamNamesArr An array of datastream name(s)
* @returns {Array} An array containing the sampling rate substring(s)
*/
const extractSamplingRateFromDatastreamName = function (datastreamNamesArr) {
// The sampling rate string is the last word in the Datastream name string
return datastreamNamesArr.map((datastreamName) =>
datastreamName.split(" ").pop()
);
};
export {
chartExportOptions,
createCombinedTextDelimitedByAmpersand,
createCombinedTextDelimitedByComma,
convertHexColorToRGBColor,
extractSamplingRateFromDatastreamName,
};
"use strict"; "use strict";
import { chartExportOptions } from "./chartExport.mjs"; import {
chartExportOptions,
extractSamplingRateFromDatastreamName,
} from "./chartHelpers.mjs";
/** /**
* Format the response from SensorThings API to make it suitable for use in a line chart * Format the response from SensorThings API to make it suitable for use in a line chart
...@@ -17,27 +20,13 @@ const formatSensorThingsApiResponseForLineChart = function (obsArray) { ...@@ -17,27 +20,13 @@ const formatSensorThingsApiResponseForLineChart = function (obsArray) {
}); });
}; };
/**
* Extracts the sampling rate substring from a datastream name string
* @param {Array} datastreamNamesArr An array of datastream name(s)
* @returns {Array} An array containing the sampling rate substring(s)
*/
const extractSamplingRateFromDatastreamName = function (datastreamNamesArr) {
// The sampling rate string is the last word in the Datastream name string
return datastreamNamesArr.map((datastreamName) =>
datastreamName.split(" ").pop()
);
};
/** /**
* Concatenates metadata properties to create a string for either the title or subtitle of a line chart * Concatenates metadata properties to create a string for either the title or subtitle of a line chart
* @param {Array} datastreamMetadataPropArr An array of metadata property strings * @param {Array} phenomenonNamesArr An array of phenomenon name strings
* @returns {String} A string of comma separated metadata property strings * @returns {String} A string made up of combined phenomenon names
*/ */
const createCombinedTextForLineChartTitles = function ( const createCombinedTextForLineChartTitles = function (phenomenonNamesArr) {
datastreamMetadataPropArr return phenomenonNamesArr.join(", ");
) {
return datastreamMetadataPropArr.join(", ");
}; };
/** /**
...@@ -50,17 +39,25 @@ const createSeriesOptionsForLineChart = function ( ...@@ -50,17 +39,25 @@ const createSeriesOptionsForLineChart = function (
formattedObsArraysForLineChart, formattedObsArraysForLineChart,
phenomenonNamesArr phenomenonNamesArr
) { ) {
// An array of colors provided by the Highcharts object // An array of colors, in hexadecimal format, provided by the global Highcharts object
const seriesColors = Highcharts.getOptions().colors; const seriesColors = Highcharts.getOptions().colors;
// Create a copy of the colors array
const seriesColorsArr = [...seriesColors];
// Create an array of seriesOptions objects // Create an array of seriesOptions objects
// Assumes that the observation array of arrays and phenomenon names array are of equal length // Assumes that the observation array of arrays and phenomenon names array are of equal length
// Use one of the arrays for looping // Use one of the arrays for looping
if (formattedObsArraysForLineChart.length !== phenomenonNamesArr.length)
throw new Error(
"The observations array and phenomenon names array have different lengths"
);
return formattedObsArraysForLineChart.map((formattedObsArray, i) => { return formattedObsArraysForLineChart.map((formattedObsArray, i) => {
return { return {
name: `${phenomenonNamesArr[i]}`, name: `${phenomenonNamesArr[i]}`,
data: formattedObsArray, data: formattedObsArray,
color: seriesColors[i], color: seriesColorsArr[i],
turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
}; };
}); });
...@@ -83,6 +80,14 @@ const drawLineChartHighcharts = function ( ...@@ -83,6 +80,14 @@ const drawLineChartHighcharts = function (
unitOfMeasurementSymbolsArr, unitOfMeasurementSymbolsArr,
} = extractedFormattedDatastreamProperties; } = extractedFormattedDatastreamProperties;
// Chart title and subtitle text
const textChartTitle =
createCombinedTextForLineChartTitles(phenomenonNamesArr);
const textChartSubtitle = `Sampling rate(s): ${createCombinedTextForLineChartTitles(
extractSamplingRateFromDatastreamName(datastreamNamesArr)
)}`;
// Create the array of series options object(s) // Create the array of series options object(s)
const seriesOptionsArr = createSeriesOptionsForLineChart( const seriesOptionsArr = createSeriesOptionsForLineChart(
formattedObsArraysForLineChart, formattedObsArraysForLineChart,
...@@ -100,14 +105,12 @@ const drawLineChartHighcharts = function ( ...@@ -100,14 +105,12 @@ const drawLineChartHighcharts = function (
}, },
title: { title: {
text: createCombinedTextForLineChartTitles(phenomenonNamesArr), text: textChartTitle,
"align": "left", "align": "left",
}, },
subtitle: { subtitle: {
text: `Sampling rate(s): ${createCombinedTextForLineChartTitles( text: textChartSubtitle,
extractSamplingRateFromDatastreamName(datastreamNamesArr)
)}`,
align: "left", align: "left",
}, },
......
"use strict"; "use strict";
import { chartExportOptions } from "./chartExport.mjs"; import {
chartExportOptions,
convertHexColorToRGBColor,
createCombinedTextDelimitedByAmpersand,
createCombinedTextDelimitedByComma,
extractSamplingRateFromDatastreamName,
} from "./chartHelpers.mjs";
/** /**
* 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 * 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
...@@ -12,15 +18,13 @@ const getSymmetricDifferenceBetweenArrays = function ( ...@@ -12,15 +18,13 @@ const getSymmetricDifferenceBetweenArrays = function (
obsTimestampArrayOne, obsTimestampArrayOne,
obsTimestampArrayTwo obsTimestampArrayTwo
) { ) {
const differenceBetweenArrays = obsTimestampArrayOne return obsTimestampArrayOne
.filter((timestampOne) => !obsTimestampArrayTwo.includes(timestampOne)) .filter((timestampOne) => !obsTimestampArrayTwo.includes(timestampOne))
.concat( .concat(
obsTimestampArrayTwo.filter( obsTimestampArrayTwo.filter(
(timestampTwo) => !obsTimestampArrayOne.includes(timestampTwo) (timestampTwo) => !obsTimestampArrayOne.includes(timestampTwo)
) )
); );
return differenceBetweenArrays;
}; };
/** /**
...@@ -33,11 +37,9 @@ const getIndexesOfUniqueObservations = function ( ...@@ -33,11 +37,9 @@ const getIndexesOfUniqueObservations = function (
uniqueTimestampsArr, uniqueTimestampsArr,
largerObsTimestampArr largerObsTimestampArr
) { ) {
const indexesMissingObs = uniqueTimestampsArr.map((index) => return uniqueTimestampsArr.map((index) =>
largerObsTimestampArr.indexOf(index) largerObsTimestampArr.indexOf(index)
); );
return indexesMissingObs;
}; };
/** /**
...@@ -213,11 +215,134 @@ const formatSensorThingsApiResponseForScatterPlot = function ( ...@@ -213,11 +215,134 @@ const formatSensorThingsApiResponseForScatterPlot = function (
return createCombinedObservationValues(obsArrayOne, obsArrayTwo); return createCombinedObservationValues(obsArrayOne, obsArrayTwo);
}; };
/**
* Concatenates metadata properties to create a string for either the title or subtitle of a scatter plot
* @param {Array} phenomenonNamesArr An array of phenomenon name strings
* @returns {String} A string made up of combined phenomenon names
*/
const createCombinedTextForScatterPlotTitles = function (phenomenonNamesArr) {
// x-axis phenomenon name is the first element of array
const phenomenonNameXAxis = phenomenonNamesArr[0];
// y-axis phenomenon name(s) array is remaining elements of array
const phenomenonNamesYAxisArr = phenomenonNamesArr.slice(1);
// Use a set to remove duplicates
const uniquePhenomenonNamesYAxis = new Set(phenomenonNamesYAxisArr);
const uniquePhenomenonNamesYAxisArr = [...uniquePhenomenonNamesYAxis];
return `${createCombinedTextDelimitedByAmpersand(
uniquePhenomenonNamesYAxisArr
)} versus ${phenomenonNameXAxis}`;
};
/**
* Create string for the x-axis title of a scatter plot
* @param {Array} phenomenonNamesArr Array of phenomenon name strings
* @param {Array} unitOfMeasurementSymbolsArr rray of unit of measurement symbol strings
* @returns {String} X-axis title string for scatter plot
*/
const createXAxisTitleTextScatterPlot = function (
phenomenonNamesArr,
unitOfMeasurementSymbolsArr
) {
// x-axis phenomenon name string is first element of array
const phenomenonNameXAxis = phenomenonNamesArr[0];
// x-axis phenomenon symbol string is first element of array
const unitOfMeasurementSymbolXAxis = unitOfMeasurementSymbolsArr[0];
return `${phenomenonNameXAxis} [${unitOfMeasurementSymbolXAxis}]`;
};
/**
* Create string for the y-axis title of a scatter plot
* @param {Array} phenomenonNamesArr Array of phenomenon name strings
* @param {Array} unitOfMeasurementSymbolsArr Array of unit of measurement symbol strings
* @returns {String} Y-axis title string for scatter plot
*/
const createYAxisTitleTextScatterPlot = function (
phenomenonNamesArr,
unitOfMeasurementSymbolsArr
) {
// y-axis phenomenon names start at array index 1
const phenomenonNamesYAxisArr = phenomenonNamesArr.slice(1);
// y-axis phenomenon symbols start at array index 1
const unitOfMeasurementSymbolsYAxisArr = unitOfMeasurementSymbolsArr.slice(1);
// The phenomenon names and unit of measurement arrays should have equal lengths
// Use one of the arrays for looping
if (
phenomenonNamesYAxisArr.length !== unitOfMeasurementSymbolsYAxisArr.length
)
throw new Error(
"The phenomenon names array and unit of measurement symbols array have different lengths"
);
const combinedNameSymbolArr = phenomenonNamesYAxisArr.map(
(phenomenonNameYAxis, i) =>
`${phenomenonNameYAxis} [${unitOfMeasurementSymbolsYAxisArr[i]}]`
);
return createCombinedTextDelimitedByComma(combinedNameSymbolArr);
};
/**
* Create an options object for each series drawn in the scatter plot
* @param {Array} formattedObsArraysForScatterPlot An array of formatted observation array(s) from one or more datastreams
* @param {Array} phenomenonNamesArr An array of phenomenon name(s)
* @returns {Array} An array made up of series options object(s)
*/
const createSeriesOptionsForScatterPlot = function (
formattedObsArraysForScatterPlot,
phenomenonNamesArr
) {
// An array of colors, in hexadecimal format, provided by the global Highcharts object
const highchartsColorsArr = Highcharts.getOptions().colors;
// Create a reversed copy of the colors array
const highchartsColorsReversedArr = [...highchartsColorsArr].reverse();
// Opacity value for symbol
const SERIES_SYMBOL_COLOR_OPACITY = ".3";
// Create array of colors in RGBA format
const seriesColors = highchartsColorsReversedArr.map(
(hexColorCode) =>
`rgba(${convertHexColorToRGBColor(
hexColorCode
)}, ${SERIES_SYMBOL_COLOR_OPACITY})`
);
// x-axis phenomenon name is the first element of array
const phenomenonNameXAxis = phenomenonNamesArr[0];
// y-axis phenomenon name(s) array is remaining elements of array
const phenomenonNamesYAxisArr = phenomenonNamesArr.slice(1);
// Create an array of seriesOptions objects
// Assumes that the observation array of arrays and phenomenon names array are of equal length
// Use one of the arrays for looping
if (
formattedObsArraysForScatterPlot.length !== phenomenonNamesYAxisArr.length
)
throw new Error(
"The observations array and phenomenon names array have different lengths"
);
return formattedObsArraysForScatterPlot.map((formattedObsArray, i) => {
return {
name: `${phenomenonNamesYAxisArr[i]}, ${phenomenonNameXAxis}`,
data: formattedObsArray,
color: seriesColors[i],
};
});
};
/** /**
* 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} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties * @param {Object} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties
* @returns {undefined} * @returns {undefined} undefined
*/ */
const drawScatterPlotHighcharts = function ( const drawScatterPlotHighcharts = function (
formattedObsArrayForSeriesOnePlusSeriesTwo, formattedObsArrayForSeriesOnePlusSeriesTwo,
...@@ -231,32 +356,32 @@ const drawScatterPlotHighcharts = function ( ...@@ -231,32 +356,32 @@ const drawScatterPlotHighcharts = function (
unitOfMeasurementSymbolsArr, unitOfMeasurementSymbolsArr,
} = extractedFormattedDatastreamProperties; } = extractedFormattedDatastreamProperties;
const [DATASTREAM_DESCRIPTION_SERIES_1, DATASTREAM_DESCRIPTION_SERIES_2] = // Create the array of series options object(s)
datastreamDescriptionsArr; const seriesOptionsArr = createSeriesOptionsForScatterPlot(
const [DATASTREAM_NAME_SERIES_1, DATASTREAM_NAME_SERIES_2] = formattedObsArrayForSeriesOnePlusSeriesTwo,
datastreamNamesArr; phenomenonNamesArr
const [PHENOMENON_NAME_SERIES_1, PHENOMENON_NAME_SERIES_2] = );
phenomenonNamesArr;
const [PHENOMENON_SYMBOL_SERIES_1, PHENOMENON_SYMBOL_SERIES_2] =
unitOfMeasurementSymbolsArr;
// Order of axes const CHART_TITLE =
// Y-Axis -- Series 2 createCombinedTextForScatterPlotTitles(phenomenonNamesArr);
// X-Axis -- Series 1
const CHART_TITLE = `${PHENOMENON_NAME_SERIES_2} Versus ${PHENOMENON_NAME_SERIES_1}`; const CHART_SUBTITLE = `Sampling rate(s): ${createCombinedTextDelimitedByComma(
const CHART_SUBTITLE = `Source: ${DATASTREAM_NAME_SERIES_2} & ${DATASTREAM_NAME_SERIES_1}`; extractSamplingRateFromDatastreamName(datastreamNamesArr)
)}`;
const SERIES_1_NAME = `${PHENOMENON_NAME_SERIES_1}`; const X_AXIS_TITLE = createXAxisTitleTextScatterPlot(
const SERIES_1_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_1}`; phenomenonNamesArr,
unitOfMeasurementSymbolsArr
);
const SERIES_2_NAME = `${PHENOMENON_NAME_SERIES_2}`; const Y_AXIS_TITLE = createYAxisTitleTextScatterPlot(
const SERIES_2_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_2}`; phenomenonNamesArr,
unitOfMeasurementSymbolsArr
);
const SERIES_COMBINED_NAME = "Y, X"; // The unit of measurement symbols for the x-axis is the first element of the array
const SERIES_COMBINED_SYMBOL_COLOR_RGB_ELEMENTS = "223, 83, 83"; // Assume that we will be comparing similar phenomena, so we can reuse this symbol
const SERIES_COMBINED_SYMBOL_COLOR_OPACITY = ".3"; const UNIT_OF_MEASUREMENT_SYMBOL = unitOfMeasurementSymbolsArr[0];
const SERIES_COMBINED_SYMBOL_COLOR = `rgba(${SERIES_COMBINED_SYMBOL_COLOR_RGB_ELEMENTS}, ${SERIES_COMBINED_SYMBOL_COLOR_OPACITY})`;
const MARKER_RADIUS = 2; const MARKER_RADIUS = 2;
...@@ -273,10 +398,12 @@ const drawScatterPlotHighcharts = function ( ...@@ -273,10 +398,12 @@ const drawScatterPlotHighcharts = function (
title: { title: {
text: CHART_TITLE, text: CHART_TITLE,
"align": "left",
}, },
subtitle: { subtitle: {
text: CHART_SUBTITLE, text: CHART_SUBTITLE,
"align": "left",
}, },
xAxis: { xAxis: {
...@@ -285,7 +412,7 @@ const drawScatterPlotHighcharts = function ( ...@@ -285,7 +412,7 @@ const drawScatterPlotHighcharts = function (
}, },
title: { title: {
enabled: true, enabled: true,
text: `${SERIES_1_NAME} [${SERIES_1_SYMBOL}]`, text: X_AXIS_TITLE,
}, },
startOnTick: true, startOnTick: true,
endOnTick: true, endOnTick: true,
...@@ -298,7 +425,7 @@ const drawScatterPlotHighcharts = function ( ...@@ -298,7 +425,7 @@ const drawScatterPlotHighcharts = function (
format: `{value}`, format: `{value}`,
}, },
title: { title: {
text: `${SERIES_2_NAME} [${SERIES_2_SYMBOL}]`, text: Y_AXIS_TITLE,
}, },
}, },
], ],
...@@ -333,22 +460,16 @@ const drawScatterPlotHighcharts = function ( ...@@ -333,22 +460,16 @@ const drawScatterPlotHighcharts = function (
const headerString = `${this.series.name}<br>`; const headerString = `${this.series.name}<br>`;
const pointString = `<b>${this.point.y.toFixed( const pointString = `<b>${this.point.y.toFixed(
2 2
)} ${SERIES_1_SYMBOL}, ${this.point.x.toFixed( )} ${UNIT_OF_MEASUREMENT_SYMBOL}, ${this.point.x.toFixed(
2 2
)} ${SERIES_2_SYMBOL}</b>`; )} ${UNIT_OF_MEASUREMENT_SYMBOL}</b>`;
return headerString + pointString; return headerString + pointString;
}, },
}, },
exporting: chartExportOptions, exporting: chartExportOptions,
series: [ series: seriesOptionsArr,
{
name: SERIES_COMBINED_NAME,
color: SERIES_COMBINED_SYMBOL_COLOR,
data: formattedObsArrayForSeriesOnePlusSeriesTwo,
},
],
}); });
}; };
......
...@@ -271,6 +271,7 @@ const getObservationsFromMultipleDatastreams = async function ( ...@@ -271,6 +271,7 @@ 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)
* @async
* @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} 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 * @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
......
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