"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); });