Commit 6c9c84ee authored by Pithon Kabiro's avatar Pithon Kabiro
Browse files

Add heatmap chart

parent 57b1db33
{
"quoteProps": "preserve"
}
## Energy Dashboard
Energy dashboard for Bosch Schwieberdingen
# iCity TP3.1 Dashboard
Development of dashboard for Bosch Schwieberdingen
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iCity Bosch Prototype</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="description" content="" />
<meta name="author" content="" />
<title>Dashboard - iCity Bosch</title>
<link href="css/styles.css" rel="stylesheet" />
<link
href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css"
rel="stylesheet"
crossorigin="anonymous"
/>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js"
crossorigin="anonymous"
></script>
<!-- Axios -->
<!-- <script src="./node_modules/axios/dist/axios.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
......@@ -21,12 +37,112 @@
href="https://cesium.com/downloads/cesiumjs/releases/1.48/Build/Cesium/Widgets/widgets.css"
rel="stylesheet"
/>
<!-- Bootstrap dashboard template -->
<script
defer
src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
crossorigin="anonymous"
></script>
<script
defer
src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"
crossorigin="anonymous"
></script>
<script defer src="js/thirdparty/scripts.js"></script>
<!--
Custom JS -->
<script defer src="js/appCesium.js"></script>
<script defer src="js/appChart.js"></script>
</head>
<body>
<div id="cesiumContainer"></div>
<!-- <div id="chart-high-line"></div> -->
<div id="chart-apex-line"></div>
<script src="js/appChart.js"></script>
<script src="js/appCesium.js"></script>
<body class="sb-nav-fixed">
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
<a class="navbar-brand" href="index.html">iCity Bosch Dashboard</a>
<button
class="btn btn-link btn-sm order-1 order-lg-0"
id="sidebarToggle"
href="#"
>
<i class="fas fa-bars"></i>
</button>
</nav>
<div id="layoutSidenav">
<div id="layoutSidenav_nav">
<nav class="sb-sidenav accordion sb-sidenav-dark" id="sidenavAccordion">
<div class="sb-sidenav-menu">
<div class="nav">
<div class="sb-sidenav-menu-heading">Core</div>
<a class="nav-link" href="index.html">
<div class="sb-nav-link-icon">
<i class="fas fa-tachometer-alt"></i>
</div>
Dashboard
</a>
</div>
</div>
</nav>
</div>
<div id="layoutSidenav_content">
<main>
<div class="container-fluid">
<h1 class="mt-4">Dashboard</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item active">Dashboard</li>
</ol>
<div class="row">
<div class="col-xl-12">
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-globe mr-1"></i>
3D Visualization
</div>
<div class="card-body">
<div id="myCesiumContainer" width="100%" height="40"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-6">
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-chart-line mr-1"></i>
Line Chart Example
</div>
<div class="card-body">
<div id="chart-apex-line" width="100%" height="40"></div>
</div>
</div>
</div>
<div class="col-xl-6">
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-chart-area mr-1"></i>
Area Chart Example
</div>
<div class="card-body">
<div id="chart-apex-heatmap" width="100%" height="40"></div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="py-4 bg-light mt-auto">
<div class="container-fluid">
<div
class="d-flex align-items-center justify-content-between small"
>
<div class="text-muted">Copyright &copy; HfT Stuttgart 2021</div>
<div>
<a href="#">Privacy Policy</a>
&middot;
<a href="#">Terms &amp; Conditions</a>
</div>
</div>
</div>
</footer>
</div>
</div>
</body>
</html>
......@@ -9,6 +9,10 @@ app.get("/", (req, res) => {
res.sendFile(__dirname + "/index.html");
});
app.get("/index.html", (req, res) => {
res.redirect("/");
});
app.listen(port, () => {
console.log(`App listening at localhost:${port}`);
});
This diff is collapsed.
......@@ -8,7 +8,7 @@ Cesium.Ion.defaultAccessToken =
const LOAD_DETAILED_BLDG225 = false;
// Global variable
const viewer = new Cesium.Viewer("cesiumContainer", {
const viewer = new Cesium.Viewer("myCesiumContainer", {
// terrainProvider: Cesium.createWorldTerrain(),
});
// viewer.scene.globe.depthTestAgainstTerrain = true;
......
// DEBUG: for testing without a browser
// const axios = require("axios").default;
"use strict";
// Request parameters
const BASE_URL =
"http://193.196.138.56/frost-icity-tp31/v1.1/Datastreams(48)/Observations";
"http://193.196.39.91:8080/frost-icity-tp31/v1.1/Datastreams(80)/Observations";
const BASE_URL2 =
"http://193.196.39.91:8080/frost-icity-tp31-v2/v1.1/Datastreams(41)/Observations";
const PARAM_RESULT_FORMAT = "dataArray";
const PARAM_ORDER_BY = "phenomenonTime asc";
const PARAM_FILTER =
"resultTime ge 2018-01-01T00:00:00.000Z and resultTime le 2018-02-01T00:00:00.000Z";
"resultTime ge 2020-01-01T00:00:00.000Z and resultTime le 2020-07-01T00:00:00.000Z";
const PARAM_SELECT = "result,phenomenonTime";
/**
* Helper function for converting the observation time from a string to Unix epoch
* @param {*} obsArray
* 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 formatObservationTime = function (obsArray) {
const dataSTAFormatted = [];
obsArray.forEach((result) => {
const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp
const valueObs = result[1];
dataSTAFormatted.push([timestampObs, valueObs]);
});
return dataSTAFormatted;
};
/**
* Function draws chart using Highcharts library
* @param {*} dataArr
*/
const drawLineChartHC = function (dataArr) {
// Create the chart
Highcharts.stockChart("chart-highcharts", {
rangeSelector: {
selected: 1,
},
title: {
text: "AAPL Stock Price",
},
series: [
{
name: "AAPL",
data: dataArr,
tooltip: {
valueDecimals: 2,
},
// turboThreshold: 10000, // Disable this option; otherwise throws error if our array has a length > 1000
},
],
});
};
/**
* Function draws chart using Apexcharts library
* @param {*} dataArr
*/
const drawLineChartAC = function (dataArr) {
const drawEmptyLineChartAC = function (
htmlElement,
mainTitle = "Main Chart Title",
yAxisTitle = "y-axis title"
) {
// Chart constants
const CHART_HTML_ELEMENT = document.querySelector("#chart-apex-line");
const CHART_NAME = "Temperature";
const TITLE_TEXT = "Inlet flow (Vorlauf)";
const Y_AXIS_TITLE = "Temperature";
const CHART_HTML_ELEMENT = htmlElement;
const TITLE_TEXT = mainTitle;
const Y_AXIS_TITLE = yAxisTitle;
const options = {
series: [
{
name: CHART_NAME,
data: dataArr,
},
],
series: [],
chart: {
type: "area",
stacked: false,
......@@ -93,6 +53,9 @@ const drawLineChartAC = function (dataArr) {
text: TITLE_TEXT,
align: "left",
},
noData: {
text: "Loading...",
},
fill: {
type: "gradient",
gradient: {
......@@ -106,8 +69,9 @@ const drawLineChartAC = function (dataArr) {
yaxis: {
labels: {
formatter: function (val) {
return val.toFixed(2);
return val.toFixed(0);
},
forceNiceScale: true,
},
title: {
text: Y_AXIS_TITLE,
......@@ -127,14 +91,296 @@ const drawLineChartAC = function (dataArr) {
};
const chart = new ApexCharts(CHART_HTML_ELEMENT, options);
return chart;
};
// Line chart 1 constants
const chart1LineHTML = document.querySelector("#chart-apex-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-apex-heatmap"),
options
);
chart.render();
};
/**
* Function follows "@iot.nextLink" links
* Follows "@iot.nextLink" links in SensorThingsAPI's response
* Appends new results to existing results
* @param {*} responsePromise
* @async
* @param {Object} responsePromise - Promise object
* @returns {Object} - Object containing results from all the "@iot.nextLink" links
*/
const followNextLink = function (responsePromise) {
return responsePromise
......@@ -182,6 +428,8 @@ followNextLink(
});
// DEBUG: Check total number of observations
console.log(combinedObservations.length);
// DEBUG: Print the array of observations
console.log(combinedObservations);
return combinedObservations;
})
......@@ -189,7 +437,6 @@ followNextLink(
console.log(err);
})
.then((observationArr) => {
const formattedObservations = formatObservationTime(observationArr);
console.log(formattedObservations);
drawLineChartAC(observationArr);
updateLineChartAC(chart1LineTitle, observationArr);
drawHeatMapAC2(observationArr);
});
/*!
* Start Bootstrap - SB Admin v6.0.2 (https://startbootstrap.com/template/sb-admin)
* Copyright 2013-2020 Start Bootstrap
* Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-sb-admin/blob/master/LICENSE)
*/
(function($) {
"use strict";
// Add active state to sidbar nav links
var path = window.location.href; // because the 'href' property of the DOM element is the absolute path
$("#layoutSidenav_nav .sb-sidenav a.nav-link").each(function() {
if (this.href === path) {
$(this).addClass("active");
}
});
// Toggle the side navigation
$("#sidebarToggle").on("click", function(e) {
e.preventDefault();
$("body").toggleClass("sb-sidenav-toggled");
});
})(jQuery);
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