"use strict";
// Request parameters
// Observations WITHOUT data gap - Bau 225
const BASE_URL =
"http://193.196.39.91:8080/frost-icity-tp31/v1.1/Datastreams(80)/Observations";
const PARAM_RESULT_FORMAT = "dataArray";
const PARAM_ORDER_BY = "phenomenonTime asc";
const PARAM_FILTER =
"resultTime ge 2020-01-01T00:00:00.000Z and resultTime le 2021-01-01T00:00:00.000Z";
const PARAM_SELECT = "result,phenomenonTime";
// Observations WITH data gap - Bau 112
const BASE_URL2 =
"http://193.196.39.91:8080/frost-icity-tp31-v2/v1.1/Datastreams(78)/Observations";
const PARAM_FILTER2 =
"resultTime ge 2020-06-01T00:00:00.000Z and resultTime le 2021-01-01T00:00:00.000Z";
/**
* Draw an EMPTY chart using Apexcharts library
* @param {HTMLElement} htmlElement - HTML element where chart will be drawn
* @param {String} mainTitle - Main chart title
* @param {String} yAxisTitle - Y-axis title
* @returns {Object} - An empty chart object
*/
const drawEmptyLineChartAC = function (
htmlElement,
mainTitle = "Main Chart Title",
yAxisTitle = "y-axis title"
) {
// Chart constants
const CHART_HTML_ELEMENT = htmlElement;
const TITLE_TEXT = mainTitle;
const Y_AXIS_TITLE = yAxisTitle;
const options = {
series: [],
chart: {
type: "area",
stacked: false,
height: 350,
zoom: {
type: "x",
enabled: true,
autoScaleYaxis: true,
},
toolbar: {
autoSelected: "zoom",
},
},
dataLabels: {
enabled: false,
},
markers: {
size: 0,
},
title: {
text: TITLE_TEXT,
align: "left",
},
noData: {
text: "Loading...",
},
fill: {
type: "gradient",
gradient: {
shadeIntensity: 1,
inverseColors: false,
opacityFrom: 0.5,
opacityTo: 0,
stops: [0, 90, 100],
},
},
yaxis: {
labels: {
formatter: function (val) {
return val.toFixed(0);
},
forceNiceScale: true,
},
title: {
text: Y_AXIS_TITLE,
},
},
xaxis: {
type: "datetime",
},
tooltip: {
shared: false,
y: {
formatter: function (val) {
return val.toFixed(2);
},
},
},
};
const chart = new ApexCharts(CHART_HTML_ELEMENT, options);
return chart;
};
// Line chart 1 constants
const chart1LineHTML = document.querySelector("#chart-line");
const chart1LineTitle = "Inlet flow (Vorlauf)";
const chart1LineYAxisTitle = "Temperature (°C)";
// Draw an empty line chart
const lineChartApex = drawEmptyLineChartAC(
chart1LineHTML,
chart1LineTitle,
chart1LineYAxisTitle
);
lineChartApex.render();
/**
* Update an empty chart created using Apexcharts library
* @param {String} chartName
* @param {Array} dataArr
* @returns {void}
*/
const updateLineChartAC = function (chartName, dataArr) {
const CHART_NAME = chartName;
// Update the chart
lineChartApex.updateSeries([
{
name: CHART_NAME,
data: dataArr,
},
]);
};
/**
* Draw a heatmap using the ApexCharts library
* ATTEMPT 1
* @param {Array} obsArray - Response from SensorThings API as array
* @returns {void}
*/
const drawHeatMapAC1 = function (obsArray) {
// Chart constants
const CHART_HEATMAP_TITLE = "HeatMap Chart";
const CHART_HEATMAP_NAME_SERIES_1 = "VL-225";
// const CHART_HEATMAP_NAME_SERIES_2 = "W2";
/**
* Convert SensorThings API response (an array) into an object
* @returns {Object} - Chart series object
*/
const generateHeatMapData = function () {
const series = [];
obsArray.forEach(([obsTime, obsValue]) => {
series.push({
x: obsTime.slice(0, -1), // remove trailing "Z" from timestamp
y: obsValue,
});
});
return series;
};
const data = [
{
name: CHART_HEATMAP_NAME_SERIES_1,
data: generateHeatMapData(obsArray),
},
// {
// name: CHART_HEATMAP_NAME_SERIES_2,
// data: generateHeatMapData(obsArray),
// },
];
data.reverse();
const colors = [
"#F3B415",
"#F27036",
"#663F59",
"#6A6E94",
"#4E88B4",
"#00A7C6",
"#18D8D8",
"#A9D794",
"#46AF78",
"#A93F55",
"#8C5E58",
"#2176FF",
"#33A1FD",
"#7A918D",
"#BAFF29",
];
// colors.reverse();
const options = {
series: data,
chart: {
height: 350,
type: "heatmap",
},
dataLabels: {
enabled: false,
},
colors: colors,
title: {
text: CHART_HEATMAP_TITLE,
},
grid: {
padding: {
right: 20,
},
},
xaxis: {
type: "datetime",
},
tooltip: {
shared: false,
x: {
formatter: function (val) {
return new Date(val).toLocaleString();
},
},
y: {
formatter: function (val) {
return val.toFixed(2);
},
},
},
plotOptions: {
heatmap: {
useFillColorAsStroke: true, // we need this option for the chart to be visible
// distributed: true,
// enableShades: false,
},
},
};
const chart = new ApexCharts(
document.querySelector("#chart-apex-heatmap"),
options
);
chart.render();
};
/**
* Draw a heatmap using the ApexCharts library
* ATTEMPT 2
* @param {Array} obsArray - Response from SensorThings API as array
* @returns {void}
*/
const drawHeatMapAC2 = function (obsArray) {
// Chart constants
const CHART_HEATMAP_TITLE = "HeatMap Chart";
const CHART_HEATMAP_NAME_SERIES_1 = "VL-225";
/**
* Convert SensorThings API response (an array) into an object
* @returns {Object} - Chart series object
*/
const generateHeatMapData = function () {
const series = [];
obsArray.forEach(([obsTime, obsValue]) => {
series.push({
x: obsTime.slice(0, -1), // remove trailing "Z" from timestamp
y: obsValue,
});
});
return series;
};
const data = [
{
name: CHART_HEATMAP_NAME_SERIES_1,
data: generateHeatMapData(obsArray),
},
// {
// name: CHART_HEATMAP_NAME_SERIES_2,
// data: generateHeatMapData(obsArray),
// },
];
// Constants for our data range
const LOW_FROM = 65;
const LOW_TO = 70;
const MEDIUM_FROM = 70;
const MEDIUM_TO = 75;
const HIGH_FROM = 75;
const HIGH_TO = 80;
const EXTREME_FROM = 80;
const EXTREME_TO = 85;
const options = {
series: data,
chart: {
height: 450,
type: "heatmap",
},
plotOptions: {
heatmap: {
shadeIntensity: 0.5,
radius: 0,
useFillColorAsStroke: true,
colorScale: {
ranges: [
{
from: null,
to: null,
name: "null",
color: "#525252",
},
{
from: LOW_FROM,
to: LOW_TO,
name: `${LOW_FROM}°C`,
color: "#1a9641",
},
{
from: MEDIUM_FROM,
to: MEDIUM_TO,
name: `${MEDIUM_FROM}°C`,
color: "#a6d96a",
},
{
from: HIGH_FROM,
to: HIGH_TO,
name: `${HIGH_FROM}°C`,
color: "#fdae61",
},
{
from: EXTREME_FROM,
to: EXTREME_TO,
name: `${EXTREME_FROM}°C`,
color: "#d7191c",
},
],
},
},
},
dataLabels: {
enabled: false,
},
stroke: {
width: 1,
},
title: {
text: CHART_HEATMAP_TITLE,
},
grid: {
padding: {
right: 20,
},
},
xaxis: {
type: "datetime",
// labels: {
// format: "MMM",
// },
},
tooltip: {
shared: false,
x: {
formatter: function (val) {
return new Date(val).toLocaleString();
},
},
y: {
formatter: function (val) {
if (val) {
return val.toFixed(2);
} else {
return "null";
}
},
},
},
// distributed: true,
};
const chart = new ApexCharts(
document.querySelector("#chart-heatmap"),
options
);
chart.render();
};
/**
* Draw a heatmap using Highcharts library
* @param {*} obsArray - Response from SensorThings API as array
* @returns {void}
*/
const drawHeatMapHC = function (obsArray) {
/**
* Format the response from SensorThings API to make it suitable for heatmap
* @returns {Array}
*/
const formatSTAResponseForHeatMap = function () {
const dataSTAFormatted = [];
obsArray.forEach((obs) => {
// Get the date/time string; first element in input array; remove trailing "Z"
const obsDateTimeInput = obs[0].slice(0, -1);
// Get the "date" part of an observation
const obsDateInput = obs[0].slice(0, 10);
// Create Date objects
const obsDateTime = new Date(obsDateTimeInput);
const obsDate = new Date(obsDateInput);
// x-axis -> timestamp; will be the same for observations from the same date
const timestamp = Date.parse(obsDate);
// y-axis -> hourOfDay
const hourOfDay = obsDateTime.getHours();
// value -> the observation's value; second element in input array
const value = obs[1];
dataSTAFormatted.push([timestamp, hourOfDay, value]);
});
return dataSTAFormatted;
};
Highcharts.chart("chart-heatmap", {
chart: {
type: "heatmap",
zoomType: "x",
},
boost: {
useGPUTranslations: true,
},
title: {
text: "Inlet flow (Vorlauf)",
align: "left",
x: 40,
},
subtitle: {
text: "Temperature variation by day and hour in 2020",
align: "left",
x: 40,
},
xAxis: {
type: "datetime",
// min: Date.UTC(2017, 0, 1),
// max: Date.UTC(2017, 11, 31, 23, 59, 59),
labels: {
align: "left",
x: 5,
y: 14,
format: "{value:%B}", // long month
},
showLastLabel: false,
tickLength: 16,
},
yAxis: {
title: {
text: null,
},
labels: {
format: "{value}:00",
},
minPadding: 0,
maxPadding: 0,
startOnTick: false,
endOnTick: false,
// tickPositions: [0, 6, 12, 18, 24],
tickPositions: [0, 3, 6, 9, 12, 15, 18, 21, 24],
tickWidth: 1,
min: 0,
max: 23,
reversed: true,
},
colorAxis: {
stops: [
[0, "#3060cf"],
[0.5, "#fffbbc"],
[0.9, "#c4463a"],
[1, "#c4463a"],
],
min: 60,
max: 85,
startOnTick: false,
endOnTick: false,
labels: {
format: "{value}℃",
},
},
series: [
{
data: formatSTAResponseForHeatMap(),
boostThreshold: 100,
borderWidth: 0,
nullColor: "#525252",
colsize: 24 * 36e5, // one day
tooltip: {
headerFormat: "Temperature
",
pointFormat:
"{point.x:%e %b, %Y} {point.y}:00: {point.value} ℃",
},
turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
},
],
});
};
/**
* Follows "@iot.nextLink" links in SensorThingsAPI's response
* Appends new results to existing results
* @async
* @param {Object} responsePromise - Promise object
* @returns {Object} - Object containing results from all the "@iot.nextLink" links
*/
const followNextLink = function (responsePromise) {
return responsePromise
.then(function (lastSuccess) {
if (lastSuccess.data["@iot.nextLink"]) {
return followNextLink(
axios.get(lastSuccess.data["@iot.nextLink"])
).then(function (nextLinkSuccess) {
nextLinkSuccess.data.value = lastSuccess.data.value.concat(
nextLinkSuccess.data.value
);
return nextLinkSuccess;
});
} else {
return lastSuccess;
}
})
.catch(function (err) {
console.log(err);
});
};
// Get "ALL" the Observations that satisfy our query
followNextLink(
axios.get(BASE_URL, {
params: {
"$resultFormat": PARAM_RESULT_FORMAT,
"$orderBy": PARAM_ORDER_BY,
"$filter": PARAM_FILTER,
"$select": PARAM_SELECT,
},
})
)
.then((success) => {
const successValue = success.data.value;
// Array that will hold the combined observations
const combinedObservations = [];
successValue.forEach((dataObj) => {
// Each page of results will have a dataArray that holds the observations
const dataArrays = dataObj.dataArray;
combinedObservations.push(...dataArrays);
});
// DEBUG: Check total number of observations
console.log(combinedObservations.length);
// DEBUG: Print the array of observations
console.log(combinedObservations);
return combinedObservations;
})
.catch((err) => {
console.log(err);
})
.then((observationArr) => {
// updateLineChartAC(chart1LineTitle, observationArr);
// drawHeatMapAC2(observationArr);
drawHeatMapHC(observationArr);
});