Commit 08df8dd6 authored by Pithon Kabiro's avatar Pithon Kabiro
Browse files

Edit function: draw scatter plot

- Move the function within the module file

- Add documentation to the function
parent a2cbd098
...@@ -464,6 +464,273 @@ const drawLineChartHC = function ( ...@@ -464,6 +464,273 @@ const drawLineChartHC = function (
}); });
}; };
/**
* Determines the timestamps that are missing from a smaller set of observations. Based on the comparison of two observation arrays, where one array is larger than the other
* @param {Array} obsTimestampArrayOne An array of timestamps for the first set of observations
* @param {Array} obsTimestampArrayTwo An array of timstamps for the second set of observations
* @returns {Array} An array of timestamps missing from either set of observations
*/
const getSymmetricDifferenceBetweenArrays = function (
obsTimestampArrayOne,
obsTimestampArrayTwo
) {
const differenceBetweenArrays = obsTimestampArrayOne
.filter((timestampOne) => !obsTimestampArrayTwo.includes(timestampOne))
.concat(
obsTimestampArrayTwo.filter(
(timestampTwo) => !obsTimestampArrayTwo.includes(timestampTwo)
)
);
return differenceBetweenArrays;
};
/**
* Determines the indexes of timestamps that are missing from a smaller set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other
* @param {Array} missingTimestampsArr An array of strings representing the missing timestamps
* @param {Array} largerObsTimestampArr An array of timestamps for the larger array of observations
* @returns {Array} An array of the indexes of the missing observations
*/
const getIndexOfMissingObservation = function (
missingTimestampsArr,
largerObsTimestampArr
) {
const indexesMissingObs = missingTimestampsArr.map((index) =>
largerObsTimestampArr.indexOf(index)
);
return indexesMissingObs;
};
/**
* Removes observations (by modifying array in place) from a larger set of observations that are missing from a smaller set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other
* @param {Array} missingIndexesArr An array of the indexes of the observations missing from the smaller set of observations
* @param {Array} largerObsArr The larger array of observations (timestamp + value) which is modified in place
* @returns {undefined}
*/
const removeMissingObservationFromLargerArray = function (
missingIndexesArr,
largerObsArr
) {
missingIndexesArr.forEach((index) => {
if (index > -1) {
largerObsArr.splice(index, 1);
}
});
};
/**
* Compares the length of two input arrays to determine the larger one
* @param {Array} firstArr First input array
* @param {Array} secondArr Second input array
* @returns {Array} The larger array
*/
const getLargerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
if (firstArr.length === secondArr.length) return;
if (firstArr.length > secondArr.length) return firstArr;
if (firstArr.length < secondArr.length) return secondArr;
};
/**
* Extracts and combines observation values from two imput observation arrays of equal length
* @param {Array} obsArrayOne First set of N observations (timestamp + value)
* @param {Array} obsArrayTwo Second set of N observations (timestamp + value)
* @returns {Array} A 2*N array of observation values from both input observation arrays
*/
const createCombinedObservationValues = function (obsArrayOne, obsArrayTwo) {
// Extract the values from the two observation arrays
const obsValuesOne = obsArrayOne.map((result) => result[1]);
const obsValuesTwo = obsArrayTwo.map((result) => result[1]);
// Since the arrays are of equal length, we need only use one of the arrays for looping
const obsValuesOnePlusTwo = obsValuesOne.map((obsValOne, i) => {
return [obsValOne, obsValuesTwo[i]];
});
return obsValuesOnePlusTwo;
};
/**
* Format the response from SensorThings API to make it suitable for scatter plot
* @param {Array} obsArrayOne Response from SensorThings API as array
* @param {Array} obsArrayTwo Response from SensorThings API as array
* @returns {Array} Array of formatted observations suitable for use in a scatter plot
*/
const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
// When our observation arrays have DIFFERENT lengths
if (obsArrayOne.length !== obsArrayTwo.length) {
// Create arrays with timestamps only
const obsArrayOneTimestamp = obsArrayOne.map(
(obsTimeValue) => obsTimeValue[0]
);
const obsArrayTwoTimestamp = obsArrayTwo.map(
(obsTimeValue) => obsTimeValue[0]
);
const missingTimestamp = getSymmetricDifferenceBetweenArrays(
obsArrayOneTimestamp,
obsArrayTwoTimestamp
);
// Determine the larger observation timestamp array
const biggerObsTimestampArr = getLargerArrayBetweenTwoInputArrays(
obsArrayOneTimestamp,
obsArrayTwoTimestamp
);
// Indexes of the missing observations
const indexesMissingObsArr = getIndexOfMissingObservation(
missingTimestamp,
biggerObsTimestampArr
);
// Determine the larger observation array
const biggerObsArr = getLargerArrayBetweenTwoInputArrays(
obsArrayOne,
obsArrayTwo
);
// Remove the missing observation from the larger array of observations
// Modifies the array in place
removeMissingObservationFromLargerArray(indexesMissingObsArr, biggerObsArr);
return createCombinedObservationValues(obsArrayOne, obsArrayTwo);
}
// When our observation arrays already have SAME lengths
return createCombinedObservationValues(obsArrayOne, obsArrayTwo);
};
/**
* Draw a scatter plot using Highcharts library
* @param {*} formattedObsArrayForSeriesOnePlusSeriesTwo Response from SensorThings API formatted for use in a scatter plot
* @param {*} formattedDatastreamMetadataSeriesOne Object containing Datastream metadata for the first chart series
* @param {*} formattedDatastreamMetadataSeriesTwo Object containing Datastream metadata for the second chart series
* @returns {undefined}
*/
const drawScatterPlotHC = function (
formattedObsArrayForSeriesOnePlusSeriesTwo,
formattedDatastreamMetadataSeriesOne,
formattedDatastreamMetadataSeriesTwo
) {
const {
datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_1,
datastreamName: DATASTREAM_NAME_SERIES_1,
phenomenonName: PHENOMENON_NAME_SERIES_1,
unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_1,
} = formattedDatastreamMetadataSeriesOne;
const {
datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_2,
datastreamName: DATASTREAM_NAME_SERIES_2,
phenomenonName: PHENOMENON_NAME_SERIES_2,
unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_2,
} = formattedDatastreamMetadataSeriesTwo;
// Order of axes
// Y-Axis -- Series 2
// X-Axis -- Series 1
const CHART_TITLE = `${PHENOMENON_NAME_SERIES_2} Versus ${PHENOMENON_NAME_SERIES_1}`;
const CHART_SUBTITLE = `Source: ${DATASTREAM_NAME_SERIES_2} & ${DATASTREAM_NAME_SERIES_1}`;
const SERIES_1_NAME = `${PHENOMENON_NAME_SERIES_1}`;
const SERIES_1_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_1}`;
const SERIES_2_NAME = `${PHENOMENON_NAME_SERIES_2}`;
const SERIES_2_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_2}`;
const SERIES_COMBINED_NAME = "Y, X";
const SERIES_COMBINED_SYMBOL_COLOR_RGB_ELEMENTS = "223, 83, 83";
const SERIES_COMBINED_SYMBOL_COLOR_OPACITY = ".3";
const SERIES_COMBINED_SYMBOL_COLOR = `rgba(${SERIES_COMBINED_SYMBOL_COLOR_RGB_ELEMENTS}, ${SERIES_COMBINED_SYMBOL_COLOR_OPACITY})`;
const MARKER_RADIUS = 2;
Highcharts.chart("chart-scatter-plot", {
chart: {
type: "scatter",
zoomType: "xy",
},
boost: {
useGPUTranslations: true,
usePreAllocated: true,
},
title: {
text: CHART_TITLE,
},
subtitle: {
text: CHART_SUBTITLE,
},
xAxis: {
labels: {
format: `{value}`,
},
title: {
enabled: true,
text: `${SERIES_1_NAME} [${SERIES_1_SYMBOL}]`,
},
startOnTick: true,
endOnTick: true,
showLastLabel: true,
},
yAxis: [
{
labels: {
format: `{value}`,
},
title: {
text: `${SERIES_2_NAME} [${SERIES_2_SYMBOL}]`,
},
},
],
legend: {
enabled: false,
},
plotOptions: {
scatter: {
marker: {
radius: MARKER_RADIUS,
states: {
hover: {
enabled: true,
lineColor: "rgb(100,100,100)",
},
},
},
states: {
hover: {
marker: {
enabled: false,
},
},
},
tooltip: {
headerFormat: "{series.name}<br>",
pointFormat: `<b>{point.y:.2f} ${SERIES_1_SYMBOL}, {point.x:.2f} ${SERIES_2_SYMBOL}</b>`,
},
},
},
series: [
{
name: SERIES_COMBINED_NAME,
color: SERIES_COMBINED_SYMBOL_COLOR,
data: formattedObsArrayForSeriesOnePlusSeriesTwo,
},
],
});
};
/** /**
* Follows "@iot.nextLink" links in SensorThingsAPI's response * Follows "@iot.nextLink" links in SensorThingsAPI's response
* Appends new results to existing results * Appends new results to existing results
...@@ -625,305 +892,14 @@ const getMetadataPlusObservationsFromMultipleDatastreams = async function ( ...@@ -625,305 +892,14 @@ const getMetadataPlusObservationsFromMultipleDatastreams = async function (
} }
}; };
const drawScatterPlotHC = function (
formattedObsArrayForSeriesOne,
formattedDatastreamMetadataSeriesOne,
formattedObsArrayForSeriesTwo,
formattedDatastreamMetadataSeriesTwo
) {
const {
datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_1,
datastreamName: DATASTREAM_NAME_SERIES_1,
phenomenonName: PHENOMENON_NAME_SERIES_1,
unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_1,
} = formattedDatastreamMetadataSeriesOne;
const {
datastreamDescription: DATASTREAM_DESCRIPTION_SERIES_2,
datastreamName: DATASTREAM_NAME_SERIES_2,
phenomenonName: PHENOMENON_NAME_SERIES_2,
unitOfMeasurementSymbol: PHENOMENON_SYMBOL_SERIES_2,
} = formattedDatastreamMetadataSeriesTwo;
const CHART_TITLE = `${PHENOMENON_NAME_SERIES_1} Versus ${PHENOMENON_NAME_SERIES_2}`;
const CHART_SUBTITLE = `Source: ${DATASTREAM_NAME_SERIES_1} & ${DATASTREAM_NAME_SERIES_2}`;
const X_AXIS_TITLE = `Date / Time`;
const SERIES_1_NAME = `${PHENOMENON_NAME_SERIES_1}`;
const SERIES_1_SYMBOL_COLOR_RGB_ELEMENTS = "119, 152, 191";
const SERIES_1_SYMBOL_COLOR = `rgba(${SERIES_1_SYMBOL_COLOR_RGB_ELEMENTS}, .5)`;
const SERIES_1_TEXT_COLOR = `rgb(${SERIES_1_SYMBOL_COLOR_RGB_ELEMENTS})`; // remove transparency from symbol color for a more "intense" color
const SERIES_1_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_1}`;
const SERIES_2_NAME = `${PHENOMENON_NAME_SERIES_2}`;
const SERIES_2_SYMBOL_COLOR_RGB_ELEMENTS = "223, 83, 83";
const SERIES_2_SYMBOL_COLOR = `rgba(${SERIES_2_SYMBOL_COLOR_RGB_ELEMENTS}, .5)`;
const SERIES_2_TEXT_COLOR = `rgb(${SERIES_2_SYMBOL_COLOR_RGB_ELEMENTS})`; // remove transparency from symbol color for a more "intense" color
const SERIES_2_SYMBOL = `${PHENOMENON_SYMBOL_SERIES_2}`;
const MARKER_RADIUS = 2;
Highcharts.chart("chart-scatter-plot", {
chart: {
type: "scatter",
zoomType: "xy",
},
boost: {
useGPUTranslations: true,
usePreAllocated: true,
},
title: {
text: CHART_TITLE,
},
subtitle: {
text: CHART_SUBTITLE,
},
xAxis: {
title: {
enabled: true,
text: X_AXIS_TITLE,
},
type: "datetime",
startOnTick: true,
endOnTick: true,
showLastLabel: true,
},
yAxis: [
{
// Primary yAxis
labels: {
format: `{value} ${SERIES_1_SYMBOL}`,
style: {
color: SERIES_1_TEXT_COLOR,
},
},
title: {
text: SERIES_1_NAME,
style: {
color: SERIES_1_TEXT_COLOR,
},
},
},
{
// Secondary yAxis
title: {
text: SERIES_2_NAME,
style: {
color: SERIES_2_TEXT_COLOR,
},
},
labels: {
format: `{value} ${SERIES_2_SYMBOL}`,
style: {
color: SERIES_2_TEXT_COLOR,
},
},
opposite: true,
},
],
// legend: {
// layout: "vertical",
// align: "left",
// verticalAlign: "top",
// x: 100,
// y: 70,
// floating: true,
// backgroundColor: Highcharts.defaultOptions.chart.backgroundColor,
// borderWidth: 1,
// },
legend: {
enabled: false,
},
plotOptions: {
scatter: {
marker: {
radius: MARKER_RADIUS,
states: {
hover: {
enabled: true,
lineColor: "rgb(100,100,100)",
},
},
},
states: {
hover: {
marker: {
enabled: false,
},
},
},
tooltip: {
headerFormat: "{point.x:%e %b, %Y %H:%M:%S}: <b>{point.y}</b>",
pointFormat: "{point.x} cm, {point.y} kg",
valueDecimals: 2,
},
},
},
series: [
{
name: SERIES_1_NAME,
color: SERIES_1_SYMBOL_COLOR,
data: formattedObsArrayForSeriesOne,
tooltip: {
valueSuffix: ` ${SERIES_1_SYMBOL}`,
},
},
{
name: SERIES_2_NAME,
color: SERIES_2_SYMBOL_COLOR,
data: formattedObsArrayForSeriesTwo,
// need this property for the dual y-axes to work
// defines the y-axis that this series refers to
yAxis: 1,
tooltip: {
valueSuffix: ` ${SERIES_2_SYMBOL}`,
},
},
],
});
};
const formatSTAResponseForScatterPlot = function (obsArrayOne, obsArrayTwo) {
// Check if the arrays have the same length
// We want `obsArrayOne` to be the larger array
const [obsArrayOneChecked, obsArrayTwoChecked] = (() => {
if (obsArrayTwo.length > obsArrayOne.length) {
return [obsArrayTwo, obsArrayOne];
} else if (
obsArrayOne.length > obsArrayTwo.length ||
obsArrayOne.length === obsArrayTwo.length
) {
return [obsArrayOne, obsArrayTwo];
}
})();
console.log(obsArrayOneChecked.length);
console.log(obsArrayTwoChecked.length);
// Create arrays with timestamps only
const obsArrayOneTimestamp = obsArrayOneChecked.map(
(obsTimeValue) => obsTimeValue[0]
);
const obsArrayTwoTimestamp = obsArrayTwoChecked.map(
(obsTimeValue) => obsTimeValue[0]
);
// console.log(obsArrayOneTimestamp);
/**
*
* @param {Array} obsTimestampArrayOne An array of timestamps for the first set of observations
* @param {Array} obsTimestampArrayTwo An array of timstamps for the second set of observations
* @returns {Array} An array of timestamps missing from the second set of observations
*/
const getMissingTimestampFromSecondArray = function (
obsTimestampArrayOne,
obsTimestampArrayTwo
) {
const differenceBetweenArrays = obsTimestampArrayOne.filter(
(timestampOne) => !obsTimestampArrayTwo.includes(timestampOne)
);
// .concat(
// obsTimestampArrayTwo.filter(
// (timestampTwo) => !obsArrayTwo.includes(timestampTwo)
// )
// );
return differenceBetweenArrays;
};
const missingTimestamp = getMissingTimestampFromSecondArray(
obsArrayOneTimestamp,
obsArrayTwoTimestamp
);
console.log(missingTimestamp);
/**
*
* @param {Array} missingTimestampsArr An array of strings representing the missing timestamps
* @param {Array} firstObsTimestampArr An array of timestamps for the first set of observations
* @returns {Array} An array of the indexes of the missing observations
*/
const getIndexOfMissingObservation = function (
missingTimestampsArr,
firstObsTimestampArr
) {
const indexesMissingObs = missingTimestampsArr.map((index) =>
firstObsTimestampArr.indexOf(index)
);
// console.log(indexes);
return indexesMissingObs;
};
const indexesMissingObsArr = getIndexOfMissingObservation(
missingTimestamp,
obsArrayOneTimestamp
);
/**
*
* @param {*} missingIndexesArr An array of the indexes of the observations missing from the second set of observations
* @param {*} obsOneArr An array of the first set of observations (timestamp + value)
* @returns {undefined}
*/
const removeMissingObservationFromFirstArray = function (
missingIndexesArr,
obsOneArr
) {
missingIndexesArr.forEach((index) => {
if (index > -1) {
obsOneArr.splice(index, 1);
}
});
};
removeMissingObservationFromFirstArray(
indexesMissingObsArr,
obsArrayOneChecked
);
console.log(obsArrayOneChecked.length);
};
(async () => {
const sensorsOfInterestArr = [
// ["225", "vl", "60min"],
["125", "rl", "60min"],
["weather_station_521", "outside_temp", "60min"],
];
const observationsPlusMetadata =
await getMetadataPlusObservationsFromMultipleDatastreams(
sensorsOfInterestArr
);
// Extract the observations and metadata for each sensor
// Array elements in same order as input array
const [
[obsSensorOneArr, obsSensorTwoArr],
[metadataSensorOne, metadataSensorTwo],
] = observationsPlusMetadata;
formatSTAResponseForScatterPlot(obsSensorOneArr, obsSensorTwoArr);
})();
/** /**
* Test drawing of scatter plot chart * Test drawing of scatter plot chart
*/ */
const drawScatterPlotHCTest = async function () { const drawScatterPlotHCTest2 = async function () {
// Input array - building, sensor, samplingRate
const sensorsOfInterestArr = [ const sensorsOfInterestArr = [
["weather_station_521", "outside_temp", "60min"],
["225", "vl", "60min"], ["225", "vl", "60min"],
// ["125", "rl", "60min"],
["weather_station_521", "outside_temp", "60min"],
]; ];
const observationsPlusMetadata = const observationsPlusMetadata =
...@@ -939,15 +915,14 @@ const drawScatterPlotHCTest = async function () { ...@@ -939,15 +915,14 @@ const drawScatterPlotHCTest = async function () {
] = observationsPlusMetadata; ] = observationsPlusMetadata;
drawScatterPlotHC( drawScatterPlotHC(
formatSTAResponseForLineChartOrScatterPlot(obsSensorOneArr), formatSTAResponseForScatterPlot(obsSensorOneArr, obsSensorTwoArr),
formatDatastreamMetadataForChart(metadataSensorOne), formatDatastreamMetadataForChart(metadataSensorOne),
formatSTAResponseForLineChartOrScatterPlot(obsSensorTwoArr),
formatDatastreamMetadataForChart(metadataSensorTwo) formatDatastreamMetadataForChart(metadataSensorTwo)
); );
}; };
(async () => { (async () => {
// await drawScatterPlotHCTest(); await drawScatterPlotHCTest2();
})(); })();
export { export {
......
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