From 86bff05081821f87c972fee28cd485a19dafcb65 Mon Sep 17 00:00:00 2001
From: Sven Schneider <icedoggy@gmx.de>
Date: Fri, 25 Jun 2021 18:14:37 +0200
Subject: [PATCH] added aggregation method into appCharts.js

---
 index.html             | 199 ++++++-------
 public/js/appCesium.js | 523 ++++++++++++++++----------------
 public/js/appChart.js  | 662 ++++++++++++++++++++++++++---------------
 3 files changed, 770 insertions(+), 614 deletions(-)

diff --git a/index.html b/index.html
index ba2082b..96efb39 100644
--- a/index.html
+++ b/index.html
@@ -1,27 +1,18 @@
 <!DOCTYPE html>
 <html lang="en">
-  <head>
+
+<head>
     <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="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"
-    />
+    <link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
 
     <!-- Font Awesome icons -->
-    <script
-      src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js"
-      crossorigin="anonymous"
-    ></script>
+    <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> -->
@@ -46,122 +37,100 @@
 
     <!-- Cesium lib -->
     <script src="https://cesium.com/downloads/cesiumjs/releases/1.48/Build/Cesium/Cesium.js"></script>
-    <link
-      href="https://cesium.com/downloads/cesiumjs/releases/1.48/Build/Cesium/Widgets/widgets.css"
-      rel="stylesheet"
-    />
+    <link 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="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 type="module" src="js/appCesium.js"></script>
     <script defer type="module" src="js/appChart.js"></script>
-  </head>
-  <body class="sb-nav-fixed">
+</head>
+
+<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="#"
-      >
+        <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 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>
-                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>
+            </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="cesiumGlobeContainer"
-                      width="100%"
-                      height="40"
-                    ></div>
-                  </div>
+                    <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="cesiumGlobeContainer" 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-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-heatmap" width="100%" height="40"></div>
+                                </div>
+                            </div>
+                        </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-line" width="100%" height="40"></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>
-              </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-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>
+            </footer>
+        </div>
     </div>
-  </body>
-</html>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/public/js/appCesium.js b/public/js/appCesium.js
index d49aa6e..ce706bb 100644
--- a/public/js/appCesium.js
+++ b/public/js/appCesium.js
@@ -2,26 +2,27 @@
 
 // Functions
 import {
-  getDatastreamIdFromBuildingNumber,
-  getObservationsUrl,
-  createTemporalFilterString,
-  formatSTAResponseForHeatMap,
-  drawHeatMapHC,
-  formatSTAResponseForLineChart,
-  drawLineChartHC,
-  followNextLink,
+    getDatastreamIdFromBuildingNumber,
+    getObservationsUrl,
+    createTemporalFilterString,
+    formatSTAResponseForHeatMap,
+    drawHeatMapHC,
+    formatSTAResponseForLineChart,
+    drawLineChartHC,
+    followNextLink,
+    aggregateResponse,
 } from "./appChart.js";
 
 // Constants
 import {
-  BASE_URL,
-  PARAM_RESULT_FORMAT,
-  PARAM_ORDER_BY,
-  PARAM_SELECT,
+    BASE_URL,
+    PARAM_RESULT_FORMAT,
+    PARAM_ORDER_BY,
+    PARAM_SELECT,
 } from "./appChart.js";
 
 Cesium.Ion.defaultAccessToken =
-  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyODgxYzJlNi1kNDZiLTQ3ZmQtYmUxYy0yMWI0OGM3NDA5MzAiLCJpZCI6NDczOSwic2NvcGVzIjpbImFzciIsImdjIl0sImlhdCI6MTU0MTUyMzU0MX0.shj2hM3pvsvcmE_wMb2aBDuk_cKWmFmbolltInGImwU";
+    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyODgxYzJlNi1kNDZiLTQ3ZmQtYmUxYy0yMWI0OGM3NDA5MzAiLCJpZCI6NDczOSwic2NvcGVzIjpbImFzciIsImdjIl0sImlhdCI6MTU0MTUyMzU0MX0.shj2hM3pvsvcmE_wMb2aBDuk_cKWmFmbolltInGImwU";
 
 // Flag to determine models that will be loaded
 // Set to `true` or `false`
@@ -29,10 +30,10 @@ const LOAD_DETAILED_BLDG225 = false;
 
 // Global variable
 const viewer = new Cesium.Viewer("cesiumGlobeContainer", {
-  scene3DOnly: true,
-  imageryProvider: Cesium.createOpenStreetMapImageryProvider({
-    url: "https://a.tile.openstreetmap.org/",
-  }),
+    scene3DOnly: true,
+    imageryProvider: Cesium.createOpenStreetMapImageryProvider({
+        url: "https://a.tile.openstreetmap.org/",
+    }),
 });
 
 /**
@@ -40,26 +41,25 @@ const viewer = new Cesium.Viewer("cesiumGlobeContainer", {
  * @param {String} urlTiles URL to the 3DTiles to be loaded
  * @returns {undefined} undefined
  */
-const loadTiles = function (urlTiles) {
-  const tileset = new Cesium.Cesium3DTileset({
-    url: urlTiles,
-  });
-  viewer.scene.primitives.add(tileset);
-
-  tileset.readyPromise.then(function () {
-    viewer
-      .zoomTo(
-        tileset,
-        new Cesium.HeadingPitchRange(
-          0.0,
-          -0.5,
-          tileset.boundingSphere.radius / 0.5
-        )
-      )
-      .otherwise(function (err) {
-        throw err;
-      });
-  });
+const loadTiles = function(urlTiles) {
+    const tileset = new Cesium.Cesium3DTileset({
+        url: urlTiles,
+    });
+    viewer.scene.primitives.add(tileset);
+
+    tileset.readyPromise.then(function() {
+        viewer
+            .zoomTo(
+                tileset,
+                new Cesium.HeadingPitchRange(
+                    0.0, -0.5,
+                    tileset.boundingSphere.radius / 0.5
+                )
+            )
+            .otherwise(function(err) {
+                throw err;
+            });
+    });
 };
 
 /**
@@ -67,12 +67,12 @@ const loadTiles = function (urlTiles) {
  * @param{*}
  * @returns {undefined} undefined
  */
-const loadNonDetailed = function () {
-  // Paths to data sources
-  const URL_3DTILES = "data_3d/3dtiles/1_full/tileset.json";
+const loadNonDetailed = function() {
+    // Paths to data sources
+    const URL_3DTILES = "data_3d/3dtiles/1_full/tileset.json";
 
-  // Tileset with all buildings
-  loadTiles(URL_3DTILES);
+    // Tileset with all buildings
+    loadTiles(URL_3DTILES);
 };
 
 /**
@@ -81,19 +81,19 @@ const loadNonDetailed = function () {
  * @param {String} gltfId Name of the glTF model file without the extension i.e. exclude the `.gltf` suffix
  * @returns {undefined} undefined
  */
-const gltfLoad = function (gltfUrl, gltfId) {
-  const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(
-    Cesium.Cartesian3.fromDegrees(9.083385, 48.881342, 0)
-  );
-
-  viewer.scene.primitives.add(
-    Cesium.Model.fromGltf({
-      url: `${gltfUrl}/${gltfId}.gltf`,
-      modelMatrix: modelMatrix,
-      scale: 0.0254,
-      allowPicking: true,
-    })
-  );
+const gltfLoad = function(gltfUrl, gltfId) {
+    const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(
+        Cesium.Cartesian3.fromDegrees(9.083385, 48.881342, 0)
+    );
+
+    viewer.scene.primitives.add(
+        Cesium.Model.fromGltf({
+            url: `${gltfUrl}/${gltfId}.gltf`,
+            modelMatrix: modelMatrix,
+            scale: 0.0254,
+            allowPicking: true,
+        })
+    );
 };
 
 /**
@@ -101,64 +101,64 @@ const gltfLoad = function (gltfUrl, gltfId) {
  * @param{*}
  * @returns {undefined} undefined
  */
-const loadDetailed = function () {
-  // Paths to data sources
-  const URL_3DTILES = "data_3d/3dtiles/2_partial/tileset.json";
-  const URL_GLTF = "data_3d/gltf";
-
-  // Tileset without building 225
-  loadTiles(URL_3DTILES);
-
-  // Load Building 225
-  gltfLoad(URL_GLTF, "bosch_si225_3");
-
-  // Load sensors in Building 225
-  const gltfArray = [
-    "sensor_013",
-    "sensor_023",
-    "sensor_033",
-    "sensor_053",
-    "sensor_063",
-    "sensor_073",
-    "sensor_083",
-    "sensor_093",
-    "sensor_103",
-    "sensor_113",
-    "sensor_123",
-    "sensor_133",
-    "sensor_143",
-    "sensor_153",
-    "sensor_163",
-    "sensor_173",
-    "sensor_183",
-    "sensor_213",
-    "sensor_223",
-    "sensor_233",
-    "sensor_253",
-    "sensor_263",
-    "sensor_273",
-    "sensor_283",
-    "sensor_293",
-    "sensor_303",
-    "sensor_313",
-    "sensor_323",
-    "sensor_333",
-    "sensor_343",
-    "sensor_353",
-    "sensor_363",
-    "sensor_373",
-    "sensor_383_v2",
-  ];
-
-  gltfArray.forEach((sensor) => gltfLoad(URL_GLTF, sensor));
+const loadDetailed = function() {
+    // Paths to data sources
+    const URL_3DTILES = "data_3d/3dtiles/2_partial/tileset.json";
+    const URL_GLTF = "data_3d/gltf";
+
+    // Tileset without building 225
+    loadTiles(URL_3DTILES);
+
+    // Load Building 225
+    gltfLoad(URL_GLTF, "bosch_si225_3");
+
+    // Load sensors in Building 225
+    const gltfArray = [
+        "sensor_013",
+        "sensor_023",
+        "sensor_033",
+        "sensor_053",
+        "sensor_063",
+        "sensor_073",
+        "sensor_083",
+        "sensor_093",
+        "sensor_103",
+        "sensor_113",
+        "sensor_123",
+        "sensor_133",
+        "sensor_143",
+        "sensor_153",
+        "sensor_163",
+        "sensor_173",
+        "sensor_183",
+        "sensor_213",
+        "sensor_223",
+        "sensor_233",
+        "sensor_253",
+        "sensor_263",
+        "sensor_273",
+        "sensor_283",
+        "sensor_293",
+        "sensor_303",
+        "sensor_313",
+        "sensor_323",
+        "sensor_333",
+        "sensor_343",
+        "sensor_353",
+        "sensor_363",
+        "sensor_373",
+        "sensor_383_v2",
+    ];
+
+    gltfArray.forEach((sensor) => gltfLoad(URL_GLTF, sensor));
 };
 
 if (!LOAD_DETAILED_BLDG225) {
-  // Default case: load only 3dTiles
-  loadNonDetailed();
+    // Default case: load only 3dTiles
+    loadNonDetailed();
 } else {
-  // Alternative case: load 3dTiles + glTF
-  loadDetailed();
+    // Alternative case: load 3dTiles + glTF
+    loadDetailed();
 }
 
 /**
@@ -166,109 +166,109 @@ if (!LOAD_DETAILED_BLDG225) {
  * @param {*}
  * @returns {undefined}
  */
-const activate3DTileFeaturePicking = function () {
-  // HTML overlay for showing feature name on mouseover
-  const nameOverlay = document.createElement("div");
-  viewer.container.appendChild(nameOverlay);
-  nameOverlay.className = "backdrop";
-  nameOverlay.style.display = "none";
-  nameOverlay.style.position = "absolute";
-  nameOverlay.style.bottom = "0";
-  nameOverlay.style.left = "0";
-  nameOverlay.style["pointer-events"] = "none";
-  nameOverlay.style.padding = "4px";
-  nameOverlay.style.backgroundColor = "black";
-  nameOverlay.style.color = "white";
-  nameOverlay.style.fontFamily = "Fira Sans, sans-serif";
-  nameOverlay.style.fontSize = "0.75em";
-
-  // Information about the currently selected feature
-  const selected = {
-    feature: undefined,
-    originalColor: new Cesium.Color(),
-  };
-
-  // An entity object which will hold info about the currently selected feature for infobox display
-  const selectedEntity = new Cesium.Entity();
-
-  // Get default left click handler for when a feature is not picked on left click
-  const clickHandler = viewer.screenSpaceEventHandler.getInputAction(
-    Cesium.ScreenSpaceEventType.LEFT_CLICK
-  );
-
-  // Change the feature color on mouse over
-
-  // Information about the currently highlighted feature
-  const highlighted = {
-    feature: undefined,
-    originalColor: new Cesium.Color(),
-  };
-
-  // Color a feature on hover.
-  viewer.screenSpaceEventHandler.setInputAction(function onMouseMove(movement) {
-    // If a feature was previously highlighted, undo the highlight
-    if (Cesium.defined(highlighted.feature)) {
-      highlighted.feature.color = highlighted.originalColor;
-      highlighted.feature = undefined;
-    }
-    // Pick a new feature
-    const pickedFeature = viewer.scene.pick(movement.endPosition);
-    if (!Cesium.defined(pickedFeature)) {
-      nameOverlay.style.display = "none";
-      return;
-    }
-    // A feature was picked, so show it's overlay content
-    nameOverlay.style.display = "block";
-    nameOverlay.style.bottom =
-      viewer.canvas.clientHeight - movement.endPosition.y + "px";
-    nameOverlay.style.left = movement.endPosition.x + "px";
-    let name = pickedFeature.getProperty("_gebaeude");
-    if (!Cesium.defined(name)) {
-      name = pickedFeature.getProperty("id");
-    }
-    nameOverlay.textContent = name;
-    // Highlight the feature if it's not already selected.
-    if (pickedFeature !== selected.feature) {
-      highlighted.feature = pickedFeature;
-      Cesium.Color.clone(pickedFeature.color, highlighted.originalColor);
-      pickedFeature.color = Cesium.Color.GREY;
-    }
-  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
-
-  // Color a feature on selection and show metadata in the InfoBox.
-  viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) {
-    // If a feature was previously selected, undo the highlight
-    if (Cesium.defined(selected.feature)) {
-      selected.feature.color = selected.originalColor;
-      selected.feature = undefined;
-    }
-    // Pick a new feature
-    const pickedFeature = viewer.scene.pick(movement.position);
-    if (!Cesium.defined(pickedFeature)) {
-      clickHandler(movement);
-      return;
-    }
-    // Select the feature if it's not already selected
-    if (selected.feature === pickedFeature) {
-      return;
-    }
-    selected.feature = pickedFeature;
-    // Save the selected feature's original color
-    if (pickedFeature === highlighted.feature) {
-      Cesium.Color.clone(highlighted.originalColor, selected.originalColor);
-      highlighted.feature = undefined;
-    } else {
-      Cesium.Color.clone(pickedFeature.color, selected.originalColor);
-    }
-    // Highlight newly selected feature
-    pickedFeature.color = Cesium.Color.LIME;
-    // Set feature infobox description
-    const featureName = pickedFeature.getProperty("name");
-    selectedEntity.name = featureName;
-    selectedEntity.description =
-      'Loading <div class="cesium-infoBox-loading"></div>';
-    viewer.selectedEntity = selectedEntity;
-    selectedEntity.description = `
+const activate3DTileFeaturePicking = function() {
+    // HTML overlay for showing feature name on mouseover
+    const nameOverlay = document.createElement("div");
+    viewer.container.appendChild(nameOverlay);
+    nameOverlay.className = "backdrop";
+    nameOverlay.style.display = "none";
+    nameOverlay.style.position = "absolute";
+    nameOverlay.style.bottom = "0";
+    nameOverlay.style.left = "0";
+    nameOverlay.style["pointer-events"] = "none";
+    nameOverlay.style.padding = "4px";
+    nameOverlay.style.backgroundColor = "black";
+    nameOverlay.style.color = "white";
+    nameOverlay.style.fontFamily = "Fira Sans, sans-serif";
+    nameOverlay.style.fontSize = "0.75em";
+
+    // Information about the currently selected feature
+    const selected = {
+        feature: undefined,
+        originalColor: new Cesium.Color(),
+    };
+
+    // An entity object which will hold info about the currently selected feature for infobox display
+    const selectedEntity = new Cesium.Entity();
+
+    // Get default left click handler for when a feature is not picked on left click
+    const clickHandler = viewer.screenSpaceEventHandler.getInputAction(
+        Cesium.ScreenSpaceEventType.LEFT_CLICK
+    );
+
+    // Change the feature color on mouse over
+
+    // Information about the currently highlighted feature
+    const highlighted = {
+        feature: undefined,
+        originalColor: new Cesium.Color(),
+    };
+
+    // Color a feature on hover.
+    viewer.screenSpaceEventHandler.setInputAction(function onMouseMove(movement) {
+        // If a feature was previously highlighted, undo the highlight
+        if (Cesium.defined(highlighted.feature)) {
+            highlighted.feature.color = highlighted.originalColor;
+            highlighted.feature = undefined;
+        }
+        // Pick a new feature
+        const pickedFeature = viewer.scene.pick(movement.endPosition);
+        if (!Cesium.defined(pickedFeature)) {
+            nameOverlay.style.display = "none";
+            return;
+        }
+        // A feature was picked, so show it's overlay content
+        nameOverlay.style.display = "block";
+        nameOverlay.style.bottom =
+            viewer.canvas.clientHeight - movement.endPosition.y + "px";
+        nameOverlay.style.left = movement.endPosition.x + "px";
+        let name = pickedFeature.getProperty("_gebaeude");
+        if (!Cesium.defined(name)) {
+            name = pickedFeature.getProperty("id");
+        }
+        nameOverlay.textContent = name;
+        // Highlight the feature if it's not already selected.
+        if (pickedFeature !== selected.feature) {
+            highlighted.feature = pickedFeature;
+            Cesium.Color.clone(pickedFeature.color, highlighted.originalColor);
+            pickedFeature.color = Cesium.Color.GREY;
+        }
+    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
+
+    // Color a feature on selection and show metadata in the InfoBox.
+    viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) {
+        // If a feature was previously selected, undo the highlight
+        if (Cesium.defined(selected.feature)) {
+            selected.feature.color = selected.originalColor;
+            selected.feature = undefined;
+        }
+        // Pick a new feature
+        const pickedFeature = viewer.scene.pick(movement.position);
+        if (!Cesium.defined(pickedFeature)) {
+            clickHandler(movement);
+            return;
+        }
+        // Select the feature if it's not already selected
+        if (selected.feature === pickedFeature) {
+            return;
+        }
+        selected.feature = pickedFeature;
+        // Save the selected feature's original color
+        if (pickedFeature === highlighted.feature) {
+            Cesium.Color.clone(highlighted.originalColor, selected.originalColor);
+            highlighted.feature = undefined;
+        } else {
+            Cesium.Color.clone(pickedFeature.color, selected.originalColor);
+        }
+        // Highlight newly selected feature
+        pickedFeature.color = Cesium.Color.LIME;
+        // Set feature infobox description
+        const featureName = pickedFeature.getProperty("name");
+        selectedEntity.name = featureName;
+        selectedEntity.description =
+            'Loading <div class="cesium-infoBox-loading"></div>';
+        viewer.selectedEntity = selectedEntity;
+        selectedEntity.description = `
     <table class="cesium-infoBox-defaultTable">
       <tbody> 
         <tr><th>Bau</th><td>
@@ -290,57 +290,62 @@ const activate3DTileFeaturePicking = function () {
     </table>
     `;
 
-    const clickedBuilding = pickedFeature.getProperty("_gebaeude");
-    const clickedBuildingDatastreamId = getDatastreamIdFromBuildingNumber(
-      clickedBuilding,
-      "vl",
-      "60min"
-    );
+        const clickedBuilding = pickedFeature.getProperty("_gebaeude");
+        const clickedBuildingDatastreamId = getDatastreamIdFromBuildingNumber(
+            clickedBuilding,
+            "vl",
+            "60min"
+        );
+
+        const BASE_URL_OBSERVATIONS = getObservationsUrl(
+            BASE_URL,
+            clickedBuildingDatastreamId
+        );
+        const PARAM_FILTER = createTemporalFilterString("2020-01-01", "2021-01-01");
+
+        const axiosGetRequest = axios.get(BASE_URL_OBSERVATIONS, {
+            params: {
+                "$resultFormat": PARAM_RESULT_FORMAT,
+                "$orderBy": PARAM_ORDER_BY,
+                "$filter": PARAM_FILTER,
+                "$select": PARAM_SELECT,
+            },
+        });
 
-    const BASE_URL_OBSERVATIONS = getObservationsUrl(
-      BASE_URL,
-      clickedBuildingDatastreamId
-    );
-    const PARAM_FILTER = createTemporalFilterString("2020-01-01", "2021-01-01");
-
-    const axiosGetRequest = axios.get(BASE_URL_OBSERVATIONS, {
-      params: {
-        "$resultFormat": PARAM_RESULT_FORMAT,
-        "$orderBy": PARAM_ORDER_BY,
-        "$filter": PARAM_FILTER,
-        "$select": PARAM_SELECT,
-      },
-    });
 
-    // Get "ALL" the Observations that satisfy our query
-    followNextLink(axiosGetRequest)
-      .then((success) => {
-        const successValue = success.data.value;
+        // Get "ALL" the Observations that satisfy our query
+        followNextLink(axiosGetRequest)
+            .then((success) => {
+                const successValue = success.data.value;
 
-        // Array that will hold the combined observations
-        const combinedObservations = [];
+                // 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;
+                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) => {
+                var agg = aggregateResponse(observationArr, 0, 'mean');
+                console.log(agg);
+                drawHeatMapHC(formatSTAResponseForHeatMap(observationArr));
+                drawLineChartHC(formatSTAResponseForLineChart(observationArr));
+            });
+
+    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
 
-          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) => {
-        drawHeatMapHC(formatSTAResponseForHeatMap(observationArr));
-        drawLineChartHC(formatSTAResponseForLineChart(observationArr));
-      });
-  }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
 };
 
-activate3DTileFeaturePicking();
+activate3DTileFeaturePicking();
\ No newline at end of file
diff --git a/public/js/appChart.js b/public/js/appChart.js
index 953024e..3ab6c6a 100644
--- a/public/js/appChart.js
+++ b/public/js/appChart.js
@@ -13,72 +13,72 @@ export const BASE_URL = "http://193.196.39.91:8080/frost-icity-tp31/v1.1";
  * @param {*} samplingRate String representing the sampling rate of the observations
  * @returns {Number} Datastream corresponding to the input building
  */
-export const getDatastreamIdFromBuildingNumber = function (
-  buildingNumber,
-  phenomenon,
-  samplingRate
+export const getDatastreamIdFromBuildingNumber = function(
+    buildingNumber,
+    phenomenon,
+    samplingRate
 ) {
-  const buildingToDatastreamMapping = {
-    101: {
-      vl: { "15min": "69", "60min": "75" },
-      rl: { "15min": "81", "60min": "87" },
-      flow: { "15min": "93", "60min": "99" },
-      power: { "15min": "105", "60min": "111" },
-      energy: { "15min": "117", "60min": "123" },
-      energy_verb: { "15min": "129", "60min": "135" },
-    },
-    102: {
-      vl: { "15min": "70", "60min": "76" },
-      rl: { "15min": "82", "60min": "88" },
-      flow: { "15min": "94", "60min": "100" },
-      power: { "15min": "106", "60min": "112" },
-      energy: { "15min": "118", "60min": "124" },
-      energy_verb: { "15min": "130", "60min": "136" },
-    },
-    107: {
-      vl: { "15min": "71", "60min": "77" },
-      rl: { "15min": "83", "60min": "89" },
-      flow: { "15min": "95", "60min": "101" },
-      power: { "15min": "107", "60min": "113" },
-      energy: { "15min": "119", "60min": "125" },
-      energy_verb: { "15min": "131", "60min": "137" },
-    },
-    "112, 118": {
-      vl: { "15min": "72", "60min": "78" },
-      rl: { "15min": "84", "60min": "90" },
-      flow: { "15min": "96", "60min": "102" },
-      power: { "15min": "108", "60min": "114" },
-      energy: { "15min": "120", "60min": "126" },
-      energy_verb: { "15min": "132", "60min": "138" },
-    },
-    125: {
-      vl: { "15min": "73", "60min": "79" },
-      rl: { "15min": "85", "60min": "91" },
-      flow: { "15min": "97", "60min": "103" },
-      power: { "15min": "109", "60min": "115" },
-      energy: { "15min": "121", "60min": "127" },
-      energy_verb: { "15min": "133", "60min": "139" },
-    },
-    225: {
-      vl: { "15min": "74", "60min": "80" },
-      rl: { "15min": "86", "60min": "92" },
-      flow: { "15min": "98", "60min": "104" },
-      power: { "15min": "110", "60min": "116" },
-      energy: { "15min": "122", "60min": "128" },
-      energy_verb: { "15min": "134", "60min": "140" },
-    },
-  };
-
-  if (!buildingNumber) return;
-
-  // check if building is contained in mapping object
-  if (!(buildingNumber in buildingToDatastreamMapping)) return;
-
-  const datastreamIdMatched = Number(
-    buildingToDatastreamMapping[buildingNumber][phenomenon][samplingRate]
-  );
-
-  return datastreamIdMatched;
+    const buildingToDatastreamMapping = {
+        101: {
+            vl: { "15min": "69", "60min": "75" },
+            rl: { "15min": "81", "60min": "87" },
+            flow: { "15min": "93", "60min": "99" },
+            power: { "15min": "105", "60min": "111" },
+            energy: { "15min": "117", "60min": "123" },
+            energy_verb: { "15min": "129", "60min": "135" },
+        },
+        102: {
+            vl: { "15min": "70", "60min": "76" },
+            rl: { "15min": "82", "60min": "88" },
+            flow: { "15min": "94", "60min": "100" },
+            power: { "15min": "106", "60min": "112" },
+            energy: { "15min": "118", "60min": "124" },
+            energy_verb: { "15min": "130", "60min": "136" },
+        },
+        107: {
+            vl: { "15min": "71", "60min": "77" },
+            rl: { "15min": "83", "60min": "89" },
+            flow: { "15min": "95", "60min": "101" },
+            power: { "15min": "107", "60min": "113" },
+            energy: { "15min": "119", "60min": "125" },
+            energy_verb: { "15min": "131", "60min": "137" },
+        },
+        "112, 118": {
+            vl: { "15min": "72", "60min": "78" },
+            rl: { "15min": "84", "60min": "90" },
+            flow: { "15min": "96", "60min": "102" },
+            power: { "15min": "108", "60min": "114" },
+            energy: { "15min": "120", "60min": "126" },
+            energy_verb: { "15min": "132", "60min": "138" },
+        },
+        125: {
+            vl: { "15min": "73", "60min": "79" },
+            rl: { "15min": "85", "60min": "91" },
+            flow: { "15min": "97", "60min": "103" },
+            power: { "15min": "109", "60min": "115" },
+            energy: { "15min": "121", "60min": "127" },
+            energy_verb: { "15min": "133", "60min": "139" },
+        },
+        225: {
+            vl: { "15min": "74", "60min": "80" },
+            rl: { "15min": "86", "60min": "92" },
+            flow: { "15min": "98", "60min": "104" },
+            power: { "15min": "110", "60min": "116" },
+            energy: { "15min": "122", "60min": "128" },
+            energy_verb: { "15min": "134", "60min": "140" },
+        },
+    };
+
+    if (!buildingNumber) return;
+
+    // check if building is contained in mapping object
+    if (!(buildingNumber in buildingToDatastreamMapping)) return;
+
+    const datastreamIdMatched = Number(
+        buildingToDatastreamMapping[buildingNumber][phenomenon][samplingRate]
+    );
+
+    return datastreamIdMatched;
 };
 
 /**
@@ -86,10 +86,10 @@ export const getDatastreamIdFromBuildingNumber = function (
  * @param {Number} datastreamID - Integer representing the Datastream ID
  * @returns {String} URL string for fetching the Observations corresponding to a Datastream
  */
-export const getObservationsUrl = function (baseUrl, datastreamID) {
-  if (!datastreamID) return;
-  const fullDatastreamURL = `${baseUrl}/Datastreams(${datastreamID})/Observations`;
-  return fullDatastreamURL;
+export const getObservationsUrl = function(baseUrl, datastreamID) {
+    if (!datastreamID) return;
+    const fullDatastreamURL = `${baseUrl}/Datastreams(${datastreamID})/Observations`;
+    return fullDatastreamURL;
 };
 
 /**
@@ -98,10 +98,10 @@ export const getObservationsUrl = function (baseUrl, datastreamID) {
  * @param {String} dateStop Stop date in YYYY-MM-DD format
  * @returns {String} Temporal filter string
  */
-export const createTemporalFilterString = function (dateStart, dateStop) {
-  if (!dateStart || !dateStop) return;
-  const filterString = `resultTime ge ${dateStart}T00:00:00.000Z and resultTime le ${dateStop}T00:00:00.000Z`;
-  return filterString;
+export const createTemporalFilterString = function(dateStart, dateStop) {
+    if (!dateStart || !dateStop) return;
+    const filterString = `resultTime ge ${dateStart}T00:00:00.000Z and resultTime le ${dateStop}T00:00:00.000Z`;
+    return filterString;
 };
 
 // const BASE_URL_OBSERVATIONS = getObservationsUrl(80);
@@ -119,31 +119,218 @@ export const PARAM_SELECT = "result,phenomenonTime";
 //   },
 // });
 
+
+/**
+ * 
+ * @param {JSON} obj JSON object on which to replace a specific key. 
+ * @param {String} oldKey is the old key in the JSON to be renamed to newKey
+ * @param {String} newKey is the key that should replace the oldKey
+ * usage: myjson.forEach((obj) => renameKey(obj, "oldkey", "newkey"));
+ */
+
+function renameKey(obj, oldKey, newKey) {
+    obj[newKey] = obj[oldKey];
+    delete obj[oldKey];
+}
+
+
+/**
+ * 
+ * @param {Array} arr is the Array to be converted into a JSON
+ * @returns {JSON} stringToJsonObject 
+ */
+function convertArray2JSON(arr) {
+    var arrayToString = JSON.stringify(Object.assign({}, arr)); // convert array to string
+    var stringToJsonObject = JSON.parse(arrayToString); // convert string to json object
+    return stringToJsonObject;
+}
+
+
+function createDateFromDateTimeString(jsonData) {
+    let datx = [];
+    let daty = [];
+    const MONTH = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+    for (var u = 0; u < jsonData.length; u++) {
+        daty.push(jsonData[u].temperature);
+        let date = new Date(jsonData[u].datetime);
+        let datum = date.getDate();
+        let month = MONTH[date.getMonth()];
+        let hour = date.getHours() + ":00";
+        let newDateStr = datum + "/" + month + "-" + hour;
+        datx.push(newDateStr);
+    }
+    return [datx, daty];
+}
+
+
+
+
+/**
+ * Format the response from SensorThings API to make it suitable for heatmap
+ * @param {Array} obsArray Response from SensorThings API as array
+ * @param {Int8} hours Number of hours to aggregate over. If hours=0, 
+ * it will be aggregated by date (e.g. all values recorded on 1st of May etc...) 
+ * if hours to any number (also hours=24) data will be aggregated over every 24hours, 
+ * even if the date changes (e.g. data from 1st of May from 10pm up 9pm on 2nd of May etc.)
+ * @param {String} method Specify how to aggregate date. Use: 'mean' = default, 'sum', 'min' or 'max'
+ * @returns {Array} Aggregated Response
+ */
+
+export const aggregateResponse = function(obsArray, hours, method) {
+    if (!obsArray) return;
+    if (hours < 0) return;
+
+    // check if we have a defined method or the method specified is accepted, rest is handeled in switch/case below
+    if (method == undefined) method = 'mean';
+
+
+    // convert obsArray to json
+
+    let jsonFromArr = [];
+    for (var i = 0; i < obsArray.length; i++) {
+        jsonFromArr.push(convertArray2JSON(obsArray[i]));
+    }
+
+    // rename the keys in the jason
+    jsonFromArr.forEach((obj) => renameKey(obj, "0", "datetime"));
+    let jsonData = jsonFromArr;
+    jsonData.forEach((obj) => renameKey(obj, "1", "temperature"));
+
+
+    let newOutput = [];
+    var aggDates = [];
+    var aggregatedVals = [];
+    var vals = []; // store values temporarily to use for processing
+
+    if (hours == 0) { // i.e. aggregate over one Date / Day
+
+        var currentDate;
+        var oldDate;
+        for (var d = 0; d < jsonData.length; d++) {
+            let tmpDate = new Date(jsonData[d].datetime);
+            currentDate = tmpDate.getDate(); // gets the day of the month 1...31
+            if (d === 0) oldDate = currentDate;
+
+            if (currentDate == oldDate) {
+                vals.push(jsonData[d].temperature);
+            } else {
+                aggDates.push(new Date(tmpDate - 1));
+
+                if (vals.length == 0) {
+                    aggregatedVals.push(-1);
+                    oldDate = currentDate;
+                    continue;
+                }
+
+                switch (method) {
+                    case 'mean':
+                        aggregatedVals.push(vals.reduce(function(a, b) { return a + b / vals.length; }, 0));
+                        break;
+                    case 'sum':
+                        aggregatedVals.push(vals.reduce(function(a, b) { return a + b; }, 0));
+                        break;
+                    case 'min':
+                        aggregatedVals.push(vals.reduce(function(a, b) { return Math.min(a, b); }));
+                        break;
+                    case 'max':
+                        aggregatedVals.push(vals.reduce(function(a, b) { return Math.max(a, b); }));
+                        break;
+                    default:
+                        aggregatedVals.push(vals.reduce(function(a, b) { return a + b / vals.length; }, 0));
+                }
+                vals = []; // clear the daily value vector 
+                vals.push(jsonData[d].temperature); // now push first entry of new day into my temp value vector.
+            }
+            oldDate = currentDate;
+        } // end of for loop
+
+        // create output to be in the same List format as the original data from obsArray.
+
+        for (let i = 0; i < aggregatedVals.length; i++) {
+            newOutput.push([aggDates[i].toISOString(), aggregatedVals[i]]);
+        }
+
+    } else { // i.e. aggregate over X hours, irrespective of the day.
+        let cnt = 0;
+        let cumHours = 0;
+
+        for (var d = 0; d < jsonData.length; d++) {
+            if (cnt < hours) {
+                vals.push(jsonData[d].temperature);
+                cnt++;
+            } else {
+                cumHours += cnt;
+                cnt = 0;
+                aggDates.push(cumHours);
+
+                if (vals.length == 0) {
+                    aggregatedVals.push(-1);
+                    continue;
+                }
+
+                switch (method) {
+                    case 'mean':
+                        aggregatedVals.push(vals.reduce(function(a, b) { return a + b / vals.length; }, 0));
+                        break;
+                    case 'sum':
+                        aggregatedVals.push(vals.reduce(function(a, b) { return a + b; }, 0));
+                        break;
+                    case 'min':
+                        aggregatedVals.push(vals.reduce(function(a, b) { return Math.min(a, b); }));
+                        break;
+                    case 'max':
+                        aggregatedVals.push(vals.reduce(function(a, b) { return Math.max(a, b); }));
+                        break;
+                    default:
+                        aggregatedVals.push(vals.reduce(function(a, b) { return a + b / vals.length; }, 0));
+                }
+                vals = []; // clear the daily value vector 
+                vals.push(jsonData[d].temperature); // now push first entry of new day into my temp value vector.
+                cnt++;
+            }
+            oldDate = currentDate;
+        } // end of for loop
+        // create output to be in the same List format as the original data from obsArray.
+
+        for (let i = 0; i < aggregatedVals.length; i++) {
+            newOutput.push([aggDates[i], aggregatedVals[i]]);
+        }
+    } // end else
+
+
+
+
+
+    return newOutput;
+}
+
+
 /**
  * Format the response from SensorThings API to make it suitable for heatmap
  * @param {Array} obsArray Response from SensorThings API as array
  * @returns {Array} Array of formatted observations suitable for use in a heatmap
  */
-export const formatSTAResponseForHeatMap = function (obsArray) {
-  if (!obsArray) return;
-  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;
+export const formatSTAResponseForHeatMap = function(obsArray) {
+    if (!obsArray) return;
+    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;
 };
 
 /**
@@ -151,94 +338,91 @@ export const formatSTAResponseForHeatMap = function (obsArray) {
  * @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap
  * @returns {Object} Highcharts library heatmap object
  */
-export const drawHeatMapHC = function (formattedObsArrayForHeatmap) {
-  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: formattedObsArrayForHeatmap,
-        boostThreshold: 100,
-        borderWidth: 0,
-        nullColor: "#525252",
-        colsize: 24 * 36e5, // one day
-        tooltip: {
-          headerFormat: "Temperature<br/>",
-          pointFormat:
-            "{point.x:%e %b, %Y} {point.y}:00: <b>{point.value} ℃</b>",
+export const drawHeatMapHC = function(formattedObsArrayForHeatmap) {
+    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,
         },
-        turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
-      },
-    ],
-  });
+
+        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: formattedObsArrayForHeatmap,
+            boostThreshold: 100,
+            borderWidth: 0,
+            nullColor: "#525252",
+            colsize: 24 * 36e5, // one day
+            tooltip: {
+                headerFormat: "Temperature<br/>",
+                pointFormat: "{point.x:%e %b, %Y} {point.y}:00: <b>{point.value} ℃</b>",
+            },
+            turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
+        }, ],
+    });
 };
 
 /**
@@ -246,15 +430,15 @@ export const drawHeatMapHC = function (formattedObsArrayForHeatmap) {
  * @param {Array} obsArray Response from SensorThings API as array
  * @returns {Array} Array of formatted observations suitable for use in a line chart
  */
-export const formatSTAResponseForLineChart = function (obsArray) {
-  if (!obsArray) return;
-  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;
+export const formatSTAResponseForLineChart = function(obsArray) {
+    if (!obsArray) return;
+    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;
 };
 
 /**
@@ -262,38 +446,36 @@ export const formatSTAResponseForLineChart = function (obsArray) {
  * @param {Array} formattedObsArrayForLineChart - Response from SensorThings API formatted for use in a line chart
  * @returns {Object} Highcharts library line chart object
  */
-export const drawLineChartHC = function (formattedObsArrayForLineChart) {
-  // Create the chart
-  Highcharts.stockChart("chart-line", {
-    chart: {
-      zoomType: "x",
-    },
-
-    rangeSelector: {
-      selected: 1,
-    },
-
-    title: {
-      text: "Inlet flow (Vorlauf)",
-      "align": "left",
-    },
-
-    subtitle: {
-      text: "Temperature variation by hour in 2020",
-      align: "left",
-    },
-
-    series: [
-      {
-        name: "AAPL",
-        data: formattedObsArrayForLineChart,
-        tooltip: {
-          valueDecimals: 2,
+export const drawLineChartHC = function(formattedObsArrayForLineChart) {
+    // Create the chart
+    Highcharts.stockChart("chart-line", {
+        chart: {
+            zoomType: "x",
+        },
+
+        rangeSelector: {
+            selected: 1,
         },
-        turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
-      },
-    ],
-  });
+
+        title: {
+            text: "Inlet flow (Vorlauf)",
+            "align": "left",
+        },
+
+        subtitle: {
+            text: "Temperature variation by hour in 2020",
+            align: "left",
+        },
+
+        series: [{
+            name: "AAPL",
+            data: formattedObsArrayForLineChart,
+            tooltip: {
+                valueDecimals: 2,
+            },
+            turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
+        }, ],
+    });
 };
 
 /**
@@ -303,26 +485,26 @@ export const drawLineChartHC = function (formattedObsArrayForLineChart) {
  * @param {Object} responsePromise Promise object
  * @returns {Object} - Object containing results from all the "@iot.nextLink" links
  */
-export const followNextLink = function (responsePromise) {
-  if (!responsePromise) return;
-  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;
+export const followNextLink = function(responsePromise) {
+    if (!responsePromise) return;
+    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);
         });
-      } else {
-        return lastSuccess;
-      }
-    })
-    .catch(function (err) {
-      console.log(err);
-    });
 };
 
 // Get "ALL" the Observations that satisfy our query
@@ -352,4 +534,4 @@ export const followNextLink = function (responsePromise) {
 //   .then((observationArr) => {
 //     drawHeatMapHC(observationArr);
 //     drawLineChartHC(observationArr);
-//   });
+//   });
\ No newline at end of file
-- 
GitLab