Commit 398cd5d8 authored by Pithon Kabiro's avatar Pithon Kabiro
Browse files

Merge branch 'wip_select-sensors-dropdown-list-8' into 'master'

Update logic for drop-down list

Refactor the logic for the drop-down lists into new module files

See merge request !23
parents 33c36fee f7b471cf
Pipeline #5170 passed with stage
in 31 seconds
...@@ -17,10 +17,9 @@ import { ...@@ -17,10 +17,9 @@ import {
extractPropertiesFromFormattedDatastreamMetadata, extractPropertiesFromFormattedDatastreamMetadata,
} from "./src_modules/fetchedDataProcessing.mjs"; } from "./src_modules/fetchedDataProcessing.mjs";
import { import { formatSensorThingsApiResponseForLineOrColumnChart } from "./src_modules/chartHelpers.mjs";
formatSensorThingsApiResponseForLineOrColumnChart,
drawLineChartHighcharts, import { drawLineChartHighcharts } from "./src_modules/chartLine.mjs";
} from "./src_modules/chartLine.mjs";
import { drawColumnChartHighcharts } from "./src_modules/chartColumn.mjs"; import { drawColumnChartHighcharts } from "./src_modules/chartColumn.mjs";
...@@ -50,12 +49,13 @@ import { ...@@ -50,12 +49,13 @@ import {
getAbbreviationsForSelectedOptionsFromAllDropDownLists, getAbbreviationsForSelectedOptionsFromAllDropDownLists,
} from "./src_modules/dropDownListHelpers.mjs"; } from "./src_modules/dropDownListHelpers.mjs";
import { import { drawColumnChartBasedOnSelectedAggregationOptions } from "./src_modules/dropDownListChartColumn.mjs";
drawHeatmapBasedOnSelectedOptions,
drawScatterPlotFromChartSelection, import { drawHeatmapBasedOnSelectedOptions } from "./src_modules/dropDownListChartHeatmap.mjs";
drawLineChartBasedOnSelectedAggregationOptions,
drawColumnChartBasedOnSelectedAggregationOptions, import { drawLineChartBasedOnSelectedAggregationOptions } from "./src_modules/dropDownListChartLine.mjs";
} from "./src_modules/dropDownListProcessing.mjs";
import { drawScatterPlotFromChartSelection } from "./src_modules/dropDownListChartScatterPlot.mjs";
/** /**
* Use the `vanillaDropDown` library to style the buildings & data points drop down list * Use the `vanillaDropDown` library to style the buildings & data points drop down list
...@@ -193,7 +193,7 @@ const drawChartUsingSelectedOptions = async function () { ...@@ -193,7 +193,7 @@ const drawChartUsingSelectedOptions = async function () {
) )
: selectedBuildingsDataPointsSamplingRateAbbrevNestedArr; : selectedBuildingsDataPointsSamplingRateAbbrevNestedArr;
// Check if we have dT (temperature difference) // Check if we have dT (temperature difference), if so, extract these options
const selectedBuildingsDataPointsSamplingRateAbbrevTempDiffArr = const selectedBuildingsDataPointsSamplingRateAbbrevTempDiffArr =
checkIfSelectedOptionsContainTemperatureDifference( checkIfSelectedOptionsContainTemperatureDifference(
selectedBuildingsDataPointsSamplingRateAbbrevNestedArr selectedBuildingsDataPointsSamplingRateAbbrevNestedArr
......
...@@ -354,10 +354,42 @@ const extractUniqueCalendarMonthsFromCalendarDates = function ( ...@@ -354,10 +354,42 @@ const extractUniqueCalendarMonthsFromCalendarDates = function (
return [...uniqueCalendarMonths]; return [...uniqueCalendarMonths];
}; };
/**
* Format a computed aggregation result to make it suitable for a chart. Currently, only line and column charts are supported
* @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 formatAggregationResultForChart = 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
if (timestampsArr.length !== aggregatedValuesArr.length) {
throw new Error(
"The timestamps array and aggregated values array have different lengths"
);
} else {
return timestampsArr.map((timestamp, i) => [
timestamp,
aggregatedValuesArr[i],
]);
}
};
export { export {
extractObservationsWithinDatesInterval, extractObservationsWithinDatesInterval,
extractObservationValuesWithinDatesInterval, extractObservationValuesWithinDatesInterval,
extractObservationValuesWithinMonthInterval, extractObservationValuesWithinMonthInterval,
extractUniqueCalendarDatesFromTimestamp, extractUniqueCalendarDatesFromTimestamp,
extractUniqueCalendarMonthsFromCalendarDates, extractUniqueCalendarMonthsFromCalendarDates,
formatAggregationResultForChart,
}; };
...@@ -9,31 +9,6 @@ import { ...@@ -9,31 +9,6 @@ import {
createTooltipDateString, createTooltipDateString,
} from "./chartHelpers.mjs"; } from "./chartHelpers.mjs";
/**
* 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 * 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} formattedAggregatedResultForColumnChart An array of formatted aggregated result array(s) from one or more datastreams
...@@ -96,12 +71,12 @@ const createYAxisTitleTextColumnChart = function ( ...@@ -96,12 +71,12 @@ const createYAxisTitleTextColumnChart = 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} formattedObsArraysForColumnChart An array made up of formatted observation array(s) suitable for use in a column chart. The observations may either be raw or aggregated
* @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} undefined * @returns {undefined} undefined
*/ */
const drawColumnChartHighcharts = function ( const drawColumnChartHighcharts = function (
formattedAggResultArraysForColumnChart, formattedObsArraysForColumnChart,
extractedFormattedDatastreamProperties extractedFormattedDatastreamProperties
) { ) {
// Formatted datastream properties // Formatted datastream properties
...@@ -136,7 +111,7 @@ const drawColumnChartHighcharts = function ( ...@@ -136,7 +111,7 @@ const drawColumnChartHighcharts = function (
// Create the array of series options object(s) // Create the array of series options object(s)
const seriesOptionsArr = createSeriesOptionsForColumnChart( const seriesOptionsArr = createSeriesOptionsForColumnChart(
formattedAggResultArraysForColumnChart, formattedObsArraysForColumnChart,
buildingIdsPhenomenonNamesArr buildingIdsPhenomenonNamesArr
); );
...@@ -213,4 +188,4 @@ const drawColumnChartHighcharts = function ( ...@@ -213,4 +188,4 @@ const drawColumnChartHighcharts = function (
}); });
}; };
export { formatAggregationResultForColumnChart, drawColumnChartHighcharts }; export { drawColumnChartHighcharts };
...@@ -57,7 +57,7 @@ const calculateMinMaxValuesForHeatmapColorAxis = function ( ...@@ -57,7 +57,7 @@ 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. Currently, only raw observations are supported, i.e. no aggregation
* @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} undefined * @returns {undefined} undefined
*/ */
......
...@@ -177,6 +177,21 @@ const checkForAndDeleteUniqueObservationsFromLargerArray = function ( ...@@ -177,6 +177,21 @@ const checkForAndDeleteUniqueObservationsFromLargerArray = function (
} }
}; };
/**
* Format the response from SensorThings API to make it suitable for use in a line chart or column chart
* @param {Array} obsArray Array of observations (timestamp + value) that is response from SensorThings API
* @returns {Array} Array of formatted observations suitable for use in a line chart
*/
const formatSensorThingsApiResponseForLineOrColumnChart = function (obsArray) {
if (!obsArray) return;
return obsArray.map((result) => {
const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp
const valueObs = result[1];
return [timestampObs, valueObs];
});
};
/** /**
* Convert a hexadecimal color code obtained from the Highcharts object (`Highcharts.getOptions().colors`) to its equivalent RGB color code * 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 * @param {String} hexCode Input hex color code
...@@ -406,6 +421,7 @@ const removeTransparencyFromColor = function (rgbaColor) { ...@@ -406,6 +421,7 @@ const removeTransparencyFromColor = function (rgbaColor) {
export { export {
chartExportOptions, chartExportOptions,
checkForAndDeleteUniqueObservationsFromLargerArray, checkForAndDeleteUniqueObservationsFromLargerArray,
formatSensorThingsApiResponseForLineOrColumnChart,
createCombinedTextDelimitedByAmpersand, createCombinedTextDelimitedByAmpersand,
createCombinedTextDelimitedByComma, createCombinedTextDelimitedByComma,
extractSamplingRateFromDatastreamName, extractSamplingRateFromDatastreamName,
......
...@@ -7,30 +7,6 @@ import { ...@@ -7,30 +7,6 @@ import {
createTooltipDateString, createTooltipDateString,
} from "./chartHelpers.mjs"; } from "./chartHelpers.mjs";
/**
* Format the response from SensorThings API to make it suitable for use in a line chart or column chart
* @param {Array} obsArray Array of observations (timestamp + value) that is response from SensorThings API
* @returns {Array} Array of formatted observations suitable for use in a line chart
*/
const formatSensorThingsApiResponseForLineOrColumnChart = function (obsArray) {
if (!obsArray) return;
return obsArray.map((result) => {
const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp
const valueObs = result[1];
return [timestampObs, valueObs];
});
};
/**
* Concatenates metadata properties to create a string for either the title or subtitle of a line chart
* @param {Array} phenomenonNamesArr An array of phenomenon name strings
* @returns {String} A string made up of combined phenomenon names
*/
const createCombinedTextForLineChartTitles = function (phenomenonNamesArr) {
return phenomenonNamesArr.join(", ");
};
/** /**
* Creates an options object for each series drawn in the line chart * Creates an options object for each series drawn in the line chart
* @param {Array} formattedObsArraysForLineChart An array of formatted observation array(s) from one or more datastreams * @param {Array} formattedObsArraysForLineChart An array of formatted observation array(s) from one or more datastreams
...@@ -42,10 +18,10 @@ const createSeriesOptionsForLineChart = function ( ...@@ -42,10 +18,10 @@ const createSeriesOptionsForLineChart = function (
buildingIdsPhenomenonNamesArr buildingIdsPhenomenonNamesArr
) { ) {
// An array of colors, in hexadecimal format, provided by the global Highcharts object // An array of colors, in hexadecimal format, provided by the global Highcharts object
const seriesColors = Highcharts.getOptions().colors; const seriesColorsArr = Highcharts.getOptions().colors;
// Create a copy of the colors array // Create a local copy of the colors array
const seriesColorsArr = [...seriesColors]; const seriesColorsForLineChartArr = [...seriesColorsArr];
// Create an array of seriesOptions objects // Create an array of seriesOptions objects
// Assumes that the observation array of arrays and building IDs + phenomenon names array are of equal length // Assumes that the observation array of arrays and building IDs + phenomenon names array are of equal length
...@@ -62,7 +38,7 @@ const createSeriesOptionsForLineChart = function ( ...@@ -62,7 +38,7 @@ const createSeriesOptionsForLineChart = function (
return { return {
name: `${buildingIdsPhenomenonNamesArr[i]}`, name: `${buildingIdsPhenomenonNamesArr[i]}`,
data: formattedObsArray, data: formattedObsArray,
color: seriesColorsArr[i], color: seriesColorsForLineChartArr[i],
turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
}; };
}); });
...@@ -71,7 +47,7 @@ const createSeriesOptionsForLineChart = function ( ...@@ -71,7 +47,7 @@ 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. The observations may either be raw or aggregated
* @param {Object} extractedFormattedDatastreamPropertiesArr An object that contains arrays of formatted Datastream properties * @param {Object} extractedFormattedDatastreamPropertiesArr An object that contains arrays of formatted Datastream properties
* @returns {undefined} undefined * @returns {undefined} undefined
*/ */
...@@ -171,7 +147,4 @@ const drawLineChartHighcharts = function ( ...@@ -171,7 +147,4 @@ const drawLineChartHighcharts = function (
}); });
}; };
export { export { drawLineChartHighcharts };
formatSensorThingsApiResponseForLineOrColumnChart,
drawLineChartHighcharts,
};
...@@ -36,20 +36,18 @@ const formatSensorThingsApiResponseForScatterPlot = function ( ...@@ -36,20 +36,18 @@ const formatSensorThingsApiResponseForScatterPlot = function (
obsArrayOne, obsArrayOne,
obsArrayTwo obsArrayTwo
) { ) {
// When our observation arrays have DIFFERENT lengths // Check if our observation arrays have equal lengths,
if (obsArrayOne.length !== obsArrayTwo.length) { // remove the unique observations, if necessary
const [obsArrayOneFinal, obsArrayTwoFinal] = const [obsArrayOneFinal, obsArrayTwoFinal] =
checkForAndDeleteUniqueObservationsFromLargerArray( obsArrayOne.length !== obsArrayTwo.length
obsArrayOne, ? checkForAndDeleteUniqueObservationsFromLargerArray(
obsArrayTwo obsArrayOne,
); obsArrayTwo
)
return createCombinedObservationValues(obsArrayOneFinal, obsArrayTwoFinal); : [obsArrayOne, obsArrayTwo];
}
// When our observation arrays already have SAME lengths // Create the combined observations array
else { return createCombinedObservationValues(obsArrayOneFinal, obsArrayTwoFinal);
return createCombinedObservationValues(obsArrayOne, obsArrayTwo);
}
}; };
/** /**
...@@ -138,14 +136,14 @@ const createSeriesOptionsForScatterPlot = function ( ...@@ -138,14 +136,14 @@ const createSeriesOptionsForScatterPlot = function (
// An array of colors, in hexadecimal format, provided by the global Highcharts object // An array of colors, in hexadecimal format, provided by the global Highcharts object
const highchartsColorsArr = Highcharts.getOptions().colors; const highchartsColorsArr = Highcharts.getOptions().colors;
// Create a reversed copy of the colors array // Create a local copy of the colors array
const highchartsColorsReversedArr = [...highchartsColorsArr].reverse(); const highchartsColorsForScatterPlotArr = [...highchartsColorsArr];
// Opacity value for symbol // Opacity value for symbol
const SERIES_SYMBOL_COLOR_OPACITY = ".3"; const SERIES_SYMBOL_COLOR_OPACITY = ".3";
// Create array of colors in RGBA format // Create array of colors in RGBA format
const seriesColors = highchartsColorsReversedArr.map( const seriesColorsArr = highchartsColorsForScatterPlotArr.map(
(hexColorCode) => (hexColorCode) =>
`rgba(${convertHexColorToRGBColor( `rgba(${convertHexColorToRGBColor(
hexColorCode hexColorCode
...@@ -171,7 +169,7 @@ const createSeriesOptionsForScatterPlot = function ( ...@@ -171,7 +169,7 @@ const createSeriesOptionsForScatterPlot = function (
return { return {
name: `${phenomenonNamesYAxisArr[i]}, ${phenomenonNameXAxis}`, name: `${phenomenonNamesYAxisArr[i]}, ${phenomenonNameXAxis}`,
data: formattedObsArray, data: formattedObsArray,
color: seriesColors[i], color: seriesColorsArr[i],
}; };
}); });
} }
...@@ -209,12 +207,12 @@ const getYAxisUnitOfMeasurementSymbol = function (seriesName) { ...@@ -209,12 +207,12 @@ const getYAxisUnitOfMeasurementSymbol = function (seriesName) {
/** /**
* 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} formattedObsArraysForScatterPlot Response from SensorThings API formatted for use in a scatter plot. Currently, only raw observations are supported, i.e. no aggregation
* @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} undefined * @returns {undefined} undefined
*/ */
const drawScatterPlotHighcharts = function ( const drawScatterPlotHighcharts = function (
formattedObsArrayForSeriesOnePlusSeriesTwo, formattedObsArraysForScatterPlot,
extractedFormattedDatastreamProperties extractedFormattedDatastreamProperties
) { ) {
// Arrays of datastream properties // Arrays of datastream properties
...@@ -227,7 +225,7 @@ const drawScatterPlotHighcharts = function ( ...@@ -227,7 +225,7 @@ const drawScatterPlotHighcharts = function (
// Create the array of series options object(s) // Create the array of series options object(s)
const seriesOptionsArr = createSeriesOptionsForScatterPlot( const seriesOptionsArr = createSeriesOptionsForScatterPlot(
formattedObsArrayForSeriesOnePlusSeriesTwo, formattedObsArraysForScatterPlot,
phenomenonNamesArr phenomenonNamesArr
); );
......
"use strict";
import {
extractUniqueCalendarMonthsFromCalendarDates,
formatAggregationResultForChart,
} from "./aggregateHelpers.mjs";
import { calculateAverageOfObservationValuesWithinInterval } from "./aggregate.mjs";
import { extractPropertiesFromFormattedDatastreamMetadata } from "./fetchedDataProcessing.mjs";
/**
* Calculate the daily average of observations and format these aggregated observations
*
* @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
* @param {Array} observationsNestedArr An array made up of sub-array(s) of observations
* @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
* @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
* @returns {Array} An array whose first element is the formatted aggregated (daily average) observations. The second element is an object made up of extracted & formatted datastream properties
*/
const calculateAndFormatDailyAverageObservations = function (
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
) {
// Calculate AVERAGE / DAILY of values of observations
const observationsAverageDailyNestedArr =
calculateAverageOfObservationValuesWithinInterval(
observationsNestedArr,
selectedSamplingRateAbbrev,
uniqueCalendarDatesNestedArr,
"daily"
);
// Format the observations
const formattedObservationsAverageDailyNestedArr =
observationsAverageDailyNestedArr.map((obsAverageDailyArr, i) =>
formatAggregationResultForChart(
uniqueCalendarDatesNestedArr[i],
obsAverageDailyArr
)
);
// Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr,
true,
"daily",
"average"
);
return [
formattedObservationsAverageDailyNestedArr,
extractedFormattedDatastreamProperties,
];
};
/**
* Calculate the monthly average of observations and format these aggregated observations
*
* @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
* @param {Array} observationsNestedArr An array made up of sub-array(s) of observations
* @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
* @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
* @returns {Array} An array whose first element is the formatted aggregated (monthly average) observations. The second element is an object made up of extracted & formatted datastream properties
*/
const calculateAndFormatMonthlyAverageObservations = function (
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
) {
// Unique calendar months
const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
(uniqueCalendarDatesArr) =>
extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
);
// Calculate AVERAGE / MONTHLY of values of observations
const observationsAverageMonthlyNestedArr =
calculateAverageOfObservationValuesWithinInterval(
observationsNestedArr,
selectedSamplingRateAbbrev,
uniqueCalendarMonthsNestedArr,
"monthly"
);
// Format the observations
const formattedObservationsAverageMonthlyNestedArr =
observationsAverageMonthlyNestedArr.map((obsAverageMonthlyArr, i) =>
formatAggregationResultForChart(
uniqueCalendarMonthsNestedArr[i],
obsAverageMonthlyArr
)
);
// Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr,
true,
"monthly",
"average"
);
return [
formattedObservationsAverageMonthlyNestedArr,
extractedFormattedDatastreamProperties,
];
};
export {
calculateAndFormatDailyAverageObservations,
calculateAndFormatMonthlyAverageObservations,
};
"use strict";
import {
extractUniqueCalendarMonthsFromCalendarDates,
formatAggregationResultForChart,
} from "./aggregateHelpers.mjs";
import { calculateMaximumObservationValuesWithinInterval } from "./aggregate.mjs";
import { extractPropertiesFromFormattedDatastreamMetadata } from "./fetchedDataProcessing.mjs";
/**
* Calculate the daily maximum of observations and format these aggregated observations
*
* @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
* @param {Array} observationsNestedArr An array made up of sub-array(s) of observations
* @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
* @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
* @returns {Array} An array whose first element is the formatted aggregated (daily maximum) observations. The second element is an object made up of extracted & formatted datastream properties
*/
const calculateAndFormatDailyMaximumObservations = function (
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
) {
// Calculate MAXIMUM / DAILY of values of observations
const observationsMaximumDailyNestedArr =
calculateMaximumObservationValuesWithinInterval(
observationsNestedArr,
selectedSamplingRateAbbrev,
uniqueCalendarDatesNestedArr,
"daily"
);
// Format the observations
const formattedObservationsMaximumDailyNestedArr =
observationsMaximumDailyNestedArr.map((obsMinDailyArr, i) =>
formatAggregationResultForChart(
uniqueCalendarDatesNestedArr[i],
obsMinDailyArr
)
);
// Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr,
true,
"daily",
"maximum"
);
return [
formattedObservationsMaximumDailyNestedArr,
extractedFormattedDatastreamProperties,
];
};
/**
* Calculate the monthly maximum of observations and format these aggregated observations
*
* @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
* @param {Array} observationsNestedArr An array made up of sub-array(s) of observations
* @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
* @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
* @returns {Array} An array whose first element is the formatted aggregated (monthly maximum) observations. The second element is an object made up of extracted & formatted datastream properties
*/
const calculateAndFormatMonthlyMaximumObservations = function (
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
) {
// Unique calendar months
const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
(uniqueCalendarDatesArr) =>
extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
);
// Calculate MAXIMUM / MONTHLY of values of observations
const observationsMaximumMonthlyNestedArr =
calculateMaximumObservationValuesWithinInterval(
observationsNestedArr,
selectedSamplingRateAbbrev,
uniqueCalendarMonthsNestedArr,
"monthly"
);
// Format the observations
const formattedObservationsMaximumMonthlyNestedArr =
observationsMaximumMonthlyNestedArr.map((obsMaxMonthlyArr, i) =>
formatAggregationResultForChart(
uniqueCalendarMonthsNestedArr[i],
obsMaxMonthlyArr
)
);
// Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr,
true,
"monthly",
"maximum"
);
return [
formattedObservationsMaximumMonthlyNestedArr,
extractedFormattedDatastreamProperties,
];
};
export {
calculateAndFormatDailyMaximumObservations,
calculateAndFormatMonthlyMaximumObservations,
};
"use strict";
import {
extractUniqueCalendarMonthsFromCalendarDates,
formatAggregationResultForChart,
} from "./aggregateHelpers.mjs";
import { calculateMinimumObservationValuesWithinInterval } from "./aggregate.mjs";
import { extractPropertiesFromFormattedDatastreamMetadata } from "./fetchedDataProcessing.mjs";
/**
* Calculate the daily minimum of observations and format these aggregated observations
*
* @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
* @param {Array} observationsNestedArr An array made up of sub-array(s) of observations
* @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
* @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
* @returns {Array} An array whose first element is the formatted aggregated (daily minimum) observations. The second element is an object made up of extracted & formatted datastream properties
*/
const calculateAndFormatDailyMinimumObservations = function (
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
) {
// Calculate MINIMUM / DAILY of values of observations
const observationsMinimumDailyNestedArr =
calculateMinimumObservationValuesWithinInterval(
observationsNestedArr,
selectedSamplingRateAbbrev,
uniqueCalendarDatesNestedArr,
"daily"
);
// Format the observations
const formattedObservationsMinimumDailyNestedArr =
observationsMinimumDailyNestedArr.map((obsMinDailyArr, i) =>
formatAggregationResultForChart(
uniqueCalendarDatesNestedArr[i],
obsMinDailyArr
)
);
// Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr,
true,
"daily",
"minimum"
);
return [
formattedObservationsMinimumDailyNestedArr,
extractedFormattedDatastreamProperties,
];
};
/**
* Calculate the monthly minimum of observations and format these aggregated observations
*
* @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
* @param {Array} observationsNestedArr An array made up of sub-array(s) of observations
* @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
* @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
* @returns {Array} An array whose first element is the formatted aggregated (monthly minimum) observations. The second element is an object made up of extracted & formatted datastream properties
*/
const calculateAndFormatMonthlyMinimumObservations = function (
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
) {
// Unique calendar months
const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
(uniqueCalendarDatesArr) =>
extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
);
// Calculate MINIMUM / MONTHLY of values of observations
const observationsMinimumMonthlyNestedArr =
calculateMinimumObservationValuesWithinInterval(
observationsNestedArr,
selectedSamplingRateAbbrev,
uniqueCalendarMonthsNestedArr,
"monthly"
);
// Format the observations
const formattedObservationsMinimumMonthlyNestedArr =
observationsMinimumMonthlyNestedArr.map((obsMinMonthlyArr, i) =>
formatAggregationResultForChart(
uniqueCalendarMonthsNestedArr[i],
obsMinMonthlyArr
)
);
// Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr,
true,
"monthly",
"minimum"
);
return [
formattedObservationsMinimumMonthlyNestedArr,
extractedFormattedDatastreamProperties,
];
};
export {
calculateAndFormatDailyMinimumObservations,
calculateAndFormatMonthlyMinimumObservations,
};
"use strict";
import {
extractUniqueCalendarMonthsFromCalendarDates,
formatAggregationResultForChart,
} from "./aggregateHelpers.mjs";
import { calculateSumOfObservationValuesWithinInterval } from "./aggregate.mjs";
import { extractPropertiesFromFormattedDatastreamMetadata } from "./fetchedDataProcessing.mjs";
/**
* Calculate the daily sum of observations and format these aggregated observations
*
* @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
* @param {Array} observationsNestedArr An array made up of sub-array(s) of observations
* @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
* @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
* @returns {Array} An array whose first element is the formatted aggregated (daily sum) observations. The second element is an object made up of extracted & formatted datastream properties
*/
const calculateAndFormatDailySumObservations = function (
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
) {
// Calculate SUM / DAILY of values of observations
const observationsSumDailyNestedArr =
calculateSumOfObservationValuesWithinInterval(
observationsNestedArr,
selectedSamplingRateAbbrev,
uniqueCalendarDatesNestedArr,
"daily"
);
// Format the observations
const formattedObservationsSumDailyNestedArr =
observationsSumDailyNestedArr.map((obsSumDailyArr, i) =>
formatAggregationResultForChart(
uniqueCalendarDatesNestedArr[i],
obsSumDailyArr
)
);
// Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr,
true,
"daily",
"sum"
);
return [
formattedObservationsSumDailyNestedArr,
extractedFormattedDatastreamProperties,
];
};
/**
* Calculate the monthly sum of observations and format these aggregated observations
*
* @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
* @param {Array} observationsNestedArr An array made up of sub-array(s) of observations
* @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
* @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
* @returns {Array} An array whose first element is the formatted aggregated (monthly sum) observations. The second element is an object made up of extracted & formatted datastream properties
*/
const calculateAndFormatMonthlySumObservations = function (
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
) {
// Unique calendar months
const uniqueCalendarMonthsNestedArr = uniqueCalendarDatesNestedArr.map(
(uniqueCalendarDatesArr) =>
extractUniqueCalendarMonthsFromCalendarDates(uniqueCalendarDatesArr)
);
// Calculate SUM / MONTHLY of values of observations
const observationsSumMonthlyNestedArr =
calculateSumOfObservationValuesWithinInterval(
observationsNestedArr,
selectedSamplingRateAbbrev,
uniqueCalendarMonthsNestedArr,
"monthly"
);
// Format the observations
const formattedObservationsSumMonthlyNestedArr =
observationsSumMonthlyNestedArr.map((obsSumMonthlyArr, i) =>
formatAggregationResultForChart(
uniqueCalendarMonthsNestedArr[i],
obsSumMonthlyArr
)
);
// Extract the formatted metadata properties
const extractedFormattedDatastreamProperties =
extractPropertiesFromFormattedDatastreamMetadata(
formattedMetadataNestedArr,
true,
"monthly",
"sum"
);
return [
formattedObservationsSumMonthlyNestedArr,
extractedFormattedDatastreamProperties,
];
};
export {
calculateAndFormatDailySumObservations,
calculateAndFormatMonthlySumObservations,
};
"use strict";
import { drawColumnChartHighcharts } from "./chartColumn.mjs";
import {
calculateAndFormatDailySumObservations,
calculateAndFormatMonthlySumObservations,
} from "./dropDownListAggregationSum.mjs";
import {
calculateAndFormatDailyMaximumObservations,
calculateAndFormatMonthlyMaximumObservations,
} from "./dropDownListAggregationMaximum.mjs";
import {
calculateAndFormatDailyMinimumObservations,
calculateAndFormatMonthlyMinimumObservations,
} from "./dropDownListAggregationMinimum.mjs";
import {
calculateAndFormatDailyAverageObservations,
calculateAndFormatMonthlyAverageObservations,
} from "./dropDownListAggregationAverage.mjs";
/**
* Draw a column chart based on the selected aggregation options from a drop-down list
*
* @param {String} selectedAggregationType A string representing the selected aggregation type. The currently supported strings include `Sum`, `Maximum`, `Minimum` and `Average`
* @param {String} selectedAggregationDuration A string representing the selected aggregation duration. The currently supported strings include `Daily` and `Monthly`
* @param {Array} observationsNestedArr An array made up of sub-array(s) of observations
* @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
* @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
* @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
* @returns {undefined} undefined
*/
export const drawColumnChartBasedOnSelectedAggregationOptions = function (
selectedAggregationType,
selectedAggregationDuration,
observationsNestedArr,
selectedSamplingRateAbbrev,
uniqueCalendarDatesNestedArr,
formattedMetadataNestedArr
) {
// Daily / sum
if (
selectedAggregationType === "Sum" &&
selectedAggregationDuration === "Daily"
) {
// Note: The `drawColumnChart` function expects two arguments,
// these are obtained by using the spread operator on the
// result returned from the `calculateAndFormat...` functions
drawColumnChartHighcharts(
...calculateAndFormatDailySumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Monthly / sum
else if (
selectedAggregationType === "Sum" &&
selectedAggregationDuration === "Monthly"
) {
drawColumnChartHighcharts(
...calculateAndFormatMonthlySumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Daily / maximum
else if (
selectedAggregationType === "Maximum" &&
selectedAggregationDuration === "Daily"
) {
drawColumnChartHighcharts(
...calculateAndFormatDailyMaximumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Monthly / maximum
else if (
selectedAggregationType === "Maximum" &&
selectedAggregationDuration === "Monthly"
) {
drawColumnChartHighcharts(
...calculateAndFormatMonthlyMaximumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Daily / minimum
else if (
selectedAggregationType === "Minimum" &&
selectedAggregationDuration === "Daily"
) {
drawColumnChartHighcharts(
...calculateAndFormatDailyMinimumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Monthly / minimum
else if (
selectedAggregationType === "Minimum" &&
selectedAggregationDuration === "Monthly"
) {
drawColumnChartHighcharts(
...calculateAndFormatMonthlyMinimumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Daily / average
else if (
selectedAggregationType === "Average" &&
selectedAggregationDuration === "Daily"
) {
drawColumnChartHighcharts(
...calculateAndFormatDailyAverageObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Monthly / average
else if (
selectedAggregationType === "Average" &&
selectedAggregationDuration === "Monthly"
) {
drawColumnChartHighcharts(
...calculateAndFormatMonthlyAverageObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
};
"use strict";
import {
formatSensorThingsApiResponseForHeatMap,
drawHeatMapHighcharts,
} from "./chartHeatmap.mjs";
/**
* Draw a heatmap based on the selected options from a drop-down list
*
* @param {Array} observationsComboNestedArr An array that contains non-computed (raw) observations and computed (temperature difference, dT) observations
* @param {Object} extractedFormattedDatastreamProperties An object that contains array(s) of formatted Datastream properties
* @returns {undefined} undefined
*/
export const drawHeatmapBasedOnSelectedOptions = function (
observationsComboNestedArr,
extractedFormattedDatastreamProperties
) {
// Create formatted array of observations
const formattedObservationsHeatMapNestedArr = observationsComboNestedArr.map(
(observationsArr) =>
formatSensorThingsApiResponseForHeatMap(observationsArr)
);
// Note: The resulting array is nested and is not suitable for heatmap,
// extract the nested array
const [formattedObservationsHeatMapArr] =
formattedObservationsHeatMapNestedArr;
drawHeatMapHighcharts(
formattedObservationsHeatMapArr,
extractedFormattedDatastreamProperties
);
};
"use strict";
import { drawLineChartHighcharts } from "./chartLine.mjs";
import {
calculateAndFormatDailySumObservations,
calculateAndFormatMonthlySumObservations,
} from "./dropDownListAggregationSum.mjs";
import {
calculateAndFormatDailyMaximumObservations,
calculateAndFormatMonthlyMaximumObservations,
} from "./dropDownListAggregationMaximum.mjs";
import {
calculateAndFormatDailyMinimumObservations,
calculateAndFormatMonthlyMinimumObservations,
} from "./dropDownListAggregationMinimum.mjs";
import {
calculateAndFormatDailyAverageObservations,
calculateAndFormatMonthlyAverageObservations,
} from "./dropDownListAggregationAverage.mjs";
/**
* Draw a line chart based on the selected aggregation options from a drop-down list
*
* @param {String} selectedAggregationType A string representing the selected aggregation type. The currently supported strings include `Sum`, `Maximum`, `Minimum` and `Average`
* @param {String} selectedAggregationDuration A string representing the selected aggregation duration. The currently supported strings include `Daily` and `Monthly`
* @param {Array} observationsNestedArr An array made up of sub-array(s) of observations
* @param {String} selectedSamplingRateAbbrev A string representing the abbreviated form of the selected sampling rate option
* @param {Array} uniqueCalendarDatesNestedArr An array made up of sub-array(s) of unique calendar date(s) string(s)
* @param {Array} formattedMetadataNestedArr An array of sub-arrays of formatted metadata properties
* @returns {undefined} undefined
*/
export const drawLineChartBasedOnSelectedAggregationOptions = function (
selectedAggregationType,
selectedAggregationDuration,
observationsNestedArr,
selectedSamplingRateAbbrev,
uniqueCalendarDatesNestedArr,
formattedMetadataNestedArr
) {
// Daily / sum
if (
selectedAggregationType === "Sum" &&
selectedAggregationDuration === "Daily"
) {
// Note: The `drawColumChart` function expects two arguments,
// these are obtained by using the spread operator on the
// result returned from the `calculateAndFormat...` functions
drawLineChartHighcharts(
...calculateAndFormatDailySumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Monthly / sum
else if (
selectedAggregationType === "Sum" &&
selectedAggregationDuration === "Monthly"
) {
drawLineChartHighcharts(
...calculateAndFormatMonthlySumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Daily / maximum
else if (
selectedAggregationType === "Maximum" &&
selectedAggregationDuration === "Daily"
) {
drawLineChartHighcharts(
...calculateAndFormatDailyMaximumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Monthly / maximum
else if (
selectedAggregationType === "Maximum" &&
selectedAggregationDuration === "Monthly"
) {
drawLineChartHighcharts(
...calculateAndFormatMonthlyMaximumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Daily / minimum
else if (
selectedAggregationType === "Minimum" &&
selectedAggregationDuration === "Daily"
) {
drawLineChartHighcharts(
...calculateAndFormatDailyMinimumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Monthly / minimum
else if (
selectedAggregationType === "Minimum" &&
selectedAggregationDuration === "Monthly"
) {
drawLineChartHighcharts(
...calculateAndFormatMonthlyMinimumObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Daily / average
else if (
selectedAggregationType === "Average" &&
selectedAggregationDuration === "Daily"
) {
drawLineChartHighcharts(
...calculateAndFormatDailyAverageObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
// Monthly / average
else if (
selectedAggregationType === "Average" &&
selectedAggregationDuration === "Monthly"
) {
drawLineChartHighcharts(
...calculateAndFormatMonthlyAverageObservations(
uniqueCalendarDatesNestedArr,
observationsNestedArr,
selectedSamplingRateAbbrev,
formattedMetadataNestedArr
)
);
}
};
"use strict";
import {
formatSensorThingsApiResponseForScatterPlot,
drawScatterPlotHighcharts,
} from "./chartScatterPlot.mjs";
/**
* Draw a scatter plot based on the selected options from a drop-down list
*
* @param {Array} observationsComboNestedArr An array that contains non-computed (raw) observations and computed (temperature difference, dT) observations
* @param {Object} extractedFormattedDatastreamProperties An object that contains array(s) of formatted Datastream properties
* @returns {undefined} undefined
*/
export const drawScatterPlotFromChartSelection = function (
observationsComboNestedArr,
extractedFormattedDatastreamProperties
) {
// Extract values for x-axis and y-axis
// x-axis values are first element of nested observations array
const [obsXAxisArr] = observationsComboNestedArr.slice(0, 1);
// y-axis values are rest of elements of nested observations array
const obsYAxisNestedArr = observationsComboNestedArr.slice(1);
// Create formatted array(s) of observations
const formattedObservationsScatterPlotArr = obsYAxisNestedArr.map(
(obsYAxisArr) =>
formatSensorThingsApiResponseForScatterPlot(obsXAxisArr, obsYAxisArr)
);
drawScatterPlotHighcharts(
formattedObservationsScatterPlotArr,
extractedFormattedDatastreamProperties
);
};
...@@ -279,14 +279,10 @@ const checkIfChartRequiresRawObservations = function ( ...@@ -279,14 +279,10 @@ const checkIfChartRequiresRawObservations = function (
selectedAggregationType, selectedAggregationType,
selectedAggregationDuration selectedAggregationDuration
) { ) {
if ( return selectedAggregationType === "None (raw data)" &&
selectedAggregationType === "None (raw data)" &&
selectedAggregationDuration === undefined selectedAggregationDuration === undefined
) { ? true
return true; : false;
} else {
return false;
}
}; };
/** /**
......
This diff is collapsed.
...@@ -35,7 +35,7 @@ const getDatastream = function (urlDatastream) { ...@@ -35,7 +35,7 @@ const getDatastream = function (urlDatastream) {
}; };
/** /**
* Perform a GET request using the Axios library * Perform a GET request to fetch Observations using the Axios library
* @param {String} urlObservations A URL that fetches Observations from an STA instance * @param {String} urlObservations A URL that fetches Observations from an STA instance
* @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
* @returns {Promise} A promise that contains the first page of results when fulfilled * @returns {Promise} A promise that contains the first page of results when fulfilled
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment