commit 2f227a6c1e1ffb03eac822931e0576a9fef25bb6
Author: ballakt <ballakt@zcu.cz>
Date:   Mon Oct 12 14:47:10 2020 +0000

    Feature Re #8156 show all datasets

diff --git a/modules/crawler/DatasetConfigs/JIS.yaml b/modules/crawler/DatasetConfigs/JIS.yaml
index 368c241..2ab5deb 100644
--- a/modules/crawler/DatasetConfigs/JIS.yaml
+++ b/modules/crawler/DatasetConfigs/JIS.yaml
@@ -1,5 +1,7 @@
 # jmeno datasetu, pod kterym bude zobrazen v aplikaci
 display-name: Snímače JIS karet
+# barva pro dataset
+display-color: '#0B155A'
 # jednoslovný název datasetu, pod kterym bude reprezentovana v architektuře
 dataset-name: JIS
 # root slozka, ktera obsahuje odkazy na dataset
diff --git a/modules/crawler/DatasetConfigs/KOLOBEZKY.yaml b/modules/crawler/DatasetConfigs/KOLOBEZKY.yaml
index f878fdc..5e3135d 100644
--- a/modules/crawler/DatasetConfigs/KOLOBEZKY.yaml
+++ b/modules/crawler/DatasetConfigs/KOLOBEZKY.yaml
@@ -1,5 +1,7 @@
 # jmeno datasetu, pod kterym bude zobrazen v aplikaci
 display-name: Půjčování koloběžek
+# barva pro dataset
+display-color: '#0048A9'
 # jednoslovný název datasetu, pod kterym bude reprezentovana v architektuře
 dataset-name: KOLOBEZKY
 # root slozka, ktera obsahuje odkazy na dataset
diff --git a/modules/crawler/DatasetConfigs/OBSAZENIMISTNOSTI.yaml b/modules/crawler/DatasetConfigs/OBSAZENIMISTNOSTI.yaml
index 443dd40..9e28e22 100644
--- a/modules/crawler/DatasetConfigs/OBSAZENIMISTNOSTI.yaml
+++ b/modules/crawler/DatasetConfigs/OBSAZENIMISTNOSTI.yaml
@@ -1,5 +1,7 @@
 # jmeno datasetu, pod kterym bude zobrazen v aplikaci
 display-name: Obsazení místností
+# barva pro dataset
+display-color: '#2c3e50'
 # jednoslovný název datasetu, pod kterym bude reprezentovana v architektuře
 dataset-name: OBSAZENIMISTNOSTI
 # root slozka, ktera obsahuje odkazy na dataset
@@ -995,4 +997,8 @@ devices:
 
   - HJ-218:
       x: UNKNOWN!
-      y: UNKNOWN!
\ No newline at end of file
+      y: UNKNOWN!
+  - CD-212:
+      x: UNKNOWN!
+      y: UNKNOWN!
+
diff --git a/modules/crawler/DatasetConfigs/WIFI.yaml b/modules/crawler/DatasetConfigs/WIFI.yaml
index 770c8d2..73bfbe2 100644
--- a/modules/crawler/DatasetConfigs/WIFI.yaml
+++ b/modules/crawler/DatasetConfigs/WIFI.yaml
@@ -1,5 +1,7 @@
 # jmeno datasetu, pod kterym bude zobrazen v aplikaci
 display-name: Počítačová síť ZČU
+# barva pro dataset
+display-color: '#574b90'
 # jednoslovný název datasetu, pod kterym bude reprezentovana v architektuře
 dataset-name: WIFI
 # root slozka, ktera obsahuje odkazy na dataset
diff --git a/modules/crawler/Utilities/Database/database_loader.py b/modules/crawler/Utilities/Database/database_loader.py
index cd1790e..c48d6ce 100644
--- a/modules/crawler/Utilities/Database/database_loader.py
+++ b/modules/crawler/Utilities/Database/database_loader.py
@@ -1,6 +1,6 @@
 from Utilities.Database import database_data_line, database_record_logs
 from Utilities import configure_functions
-from Utilities.helpers import should_skip
+from Utilities.helpers import should_skip, detect_change
 import pymongo
 import re
 
@@ -24,7 +24,7 @@ PROCESSED_DATA_PATH = "ProcessedData/"
 def create_database_connection():
     """
     Creates connection to mongoDB
-    
+
     Returns:
         Connection to mongoDB
     """
@@ -116,25 +116,27 @@ def check_or_update_datasets_collection(database_connection, config):
         config: loaded configuration file of dataset
     """
     # collection where are specified aviable datasets
+    compareKeys = ['display-name',
+                   'display-color']
     collection_datasets = database_connection[MONGODB_DATASET_COLLECTION]
 
-    dataset_name = config['dataset-name']
-    display_name = config['display-name']
-
-    query = {'key-name': dataset_name}
+    query = {'key-name': config['dataset-name']}
 
     # check if newly added data already have a dataset specified in collection
-    dataset_present = collection_datasets.find_one(query)
+    current_dataset = collection_datasets.find_one(query)
 
-    if dataset_present is None:
+    if current_dataset is None:
         collection_datasets.insert_one({
-            'key-name': dataset_name,
-            'display-name': display_name,
+            'key-name': config['dataset-name'],
+            'display-name': config['display-name'],
+            'display-color': config['display-color'],
             'updated': 0
         })
-    elif dataset_present['display-name'] != display_name:
-        newvalues = {"$set": {'display-name': display_name}}
-        collection_datasets.update_one(query, newvalues)
+    elif detect_change(current_dataset, config, compareKeys):
+        newVal = {}
+        for key in compareKeys:
+            newVal[key] = config[key]
+        collection_datasets.update_one(query, {"$set": newVal})
 
 
 def update_devices_collection(config):
@@ -252,4 +254,4 @@ def reset_dataset_database(dataset_name):
             mydb[name].drop()
             print("Dropping: " + name)
 
-    database_record_logs.reset_ignore_set_loaded(dataset_name)
\ No newline at end of file
+    database_record_logs.reset_ignore_set_loaded(dataset_name)
diff --git a/modules/crawler/Utilities/helpers.py b/modules/crawler/Utilities/helpers.py
index 6dd4b9d..4d5e0c7 100644
--- a/modules/crawler/Utilities/helpers.py
+++ b/modules/crawler/Utilities/helpers.py
@@ -1,3 +1,5 @@
+from typing import Dict
+
 SKIP = "SKIP"
 UNKNOWN = "UNKNOWN!"
 
@@ -5,3 +7,22 @@ UNKNOWN = "UNKNOWN!"
 def should_skip(device) -> bool:
     return device['x'] == SKIP or device['y'] == SKIP or device[
         'x'] == UNKNOWN or device['y'] == UNKNOWN
+
+
+def detect_change(first: Dict[str, str], second: Dict[str, str], compareKeys: [str]) -> bool:
+    """Detects change between two dictonaries
+
+    Args:
+        first (Dict[str, str]): First dictionary
+        second (Dict[str, str]): Second dictionary
+        compareKeys ([type]): Keys to handle comparison
+
+    Returns:
+        bool: Is there a change ?
+    """
+    for key in compareKeys:
+        if key not in second or key not in first:
+            return True
+        if first[key] != second[key]:
+            return True
+    return False
diff --git a/modules/crawler/prepare_new_dataset.py b/modules/crawler/prepare_new_dataset.py
index 482397c..dcf1860 100644
--- a/modules/crawler/prepare_new_dataset.py
+++ b/modules/crawler/prepare_new_dataset.py
@@ -10,9 +10,11 @@ CRAWLER_PROGRAM_PATH = "DatasetCrawler"
 PROCESSOR_PROGRAM_PATH = "DatasetProcessing"
 # Path to dataset configuration files
 CONFIG_FILES_PATH = "DatasetConfigs"
+# Default color for visualization of dataset (buble info in map)
+DEFAULT_COLOR = "#000000"
 
 
-def create_default_config_file(dataset_name):
+def create_default_config_file(dataset_name: str):
     """
     Creates default config file
 
@@ -22,11 +24,16 @@ def create_default_config_file(dataset_name):
     with open(CONFIG_FILES_PATH + "/" + dataset_name + ".yaml", "w") as file:
         file.write("# jmeno datasetu, pod kterym bude zobrazen v aplikaci\n")
         file.write("display-name: " + dataset_name + "\n")
-        file.write("# jednoslovný název datasetu, pod kterym bude reprezentovana v architektuře\n")
+        file.write(
+            "# jednoslovný název datasetu, pod kterym bude reprezentovana v architektuře\n")
+        file.write("display-color: " + DEFAULT_COLOR + "\n")
+        file.write(
+            "# barva pro tento dataset v hexadecimální hodnotě (#000000)\n")
         file.write("dataset-name: " + dataset_name + "\n")
         file.write("# root slozka, ktera obsahuje odkazy na dataset\n")
         file.write("url: ZDE VLOZTE URL\n")
-        file.write("# volitelny parameter, ktery specifikuje vzor jmrna datasetu, ktera se budou stahovat\n")
+        file.write(
+            "# volitelny parameter, ktery specifikuje vzor jmrna datasetu, ktera se budou stahovat\n")
         file.write("regex: ZDE VLOZTE REGEX\n")
         file.write("# volitelny parametr, ktery udava jak casto se budou hledat nove datasety, pokud prazdne, "
                    "tak defaultni hodnota (dny)\n")
@@ -48,10 +55,14 @@ def create_default_processor(dataset_name):
         file.write("\n")
         file.write("def process_file(filename):\n")
         file.write("    \"\"\"\n")
-        file.write("    Method that take path to crawled file and outputs date dictionary:\n")
-        file.write("    Date dictionary is a dictionary where keys are dates in format YYYY-mm-dd-hh (2018-04-08-15)\n")
-        file.write("    and value is dictionary where keys are devices (specified in configuration file)\n")
-        file.write("    and value is CSVDataLine.csv_data_line with device,date and occurrence\n")
+        file.write(
+            "    Method that take path to crawled file and outputs date dictionary:\n")
+        file.write(
+            "    Date dictionary is a dictionary where keys are dates in format YYYY-mm-dd-hh (2018-04-08-15)\n")
+        file.write(
+            "    and value is dictionary where keys are devices (specified in configuration file)\n")
+        file.write(
+            "    and value is CSVDataLine.csv_data_line with device,date and occurrence\n")
         file.write("\n")
         file.write("    Args:\n")
         file.write("    filename: name of processed file\n")
@@ -63,7 +74,8 @@ def create_default_processor(dataset_name):
         file.write("    date_dict = dict()\n")
         file.write("\n")
         file.write("    #with open(filename, \"r\") as file:\n")
-        file.write("    print(\"You must implements process_file method first!\")\n")
+        file.write(
+            "    print(\"You must implements process_file method first!\")\n")
         file.write("    return None\n")
 
 
@@ -82,10 +94,12 @@ def create_default_crawler(dataset_name):
         file.write("\n")
         file.write("def crawl(config):\n")
         file.write("    \"\"\"\n")
-        file.write("    Implement crawl method that downloads new data to path_for_files\n")
+        file.write(
+            "    Implement crawl method that downloads new data to path_for_files\n")
         file.write("    For keeping the project structure\n")
         file.write("    url , regex, and dataset_name from config\n")
-        file.write("    You can use already implemented functions from Utilities/Crawler/BasicCrawlerFunctions.py\n")
+        file.write(
+            "    You can use already implemented functions from Utilities/Crawler/BasicCrawlerFunctions.py\n")
         file.write("\n")
         file.write("    Args:\n")
         file.write("        config: loaded configuration file of dataset\n")
@@ -93,7 +107,8 @@ def create_default_crawler(dataset_name):
         file.write("    dataset_name = config[\"dataset-name\"]\n")
         file.write("    url = config['url']\n")
         file.write("    regex = config['regex']\n")
-        file.write("    path_for_files = CRAWLED_DATA_PATH + dataset_name + '/'\n")
+        file.write(
+            "    path_for_files = CRAWLED_DATA_PATH + dataset_name + '/'\n")
         file.write("    print(\"You must implements Crawl method first!\")\n")
 
 
@@ -123,6 +138,7 @@ def prepare_dataset_structure(dataset_name):
     create_default_processor(dataset_name)
     create_default_config_file(dataset_name)
 
+
 print("Zadejte jméno nového datasetu:\n")
 
 dataset_name = input().upper()
diff --git a/website/.devcontainer/devcontainer.json b/website/.devcontainer/devcontainer.json
index 461e24c..9b10898 100644
--- a/website/.devcontainer/devcontainer.json
+++ b/website/.devcontainer/devcontainer.json
@@ -1,5 +1,8 @@
 {
-    "forwardPorts": [8000],
+    "forwardPorts": [
+        8000,
+        27017
+    ],
     "extensions": [
         "vscode-icons-team.vscode-icons",
         "felixfbecker.php-intellisense",
@@ -7,11 +10,16 @@
         "felixfbecker.php-debug",
         "junstyle.php-cs-fixer",
         "dbaeumer.vscode-eslint",
-        "refgd.easy-compile"
+        "refgd.easy-compile",
+        "mongodb.mongodb-vscode"
     ],
     "workspaceFolder": "/var/www/symfony",
     "service": "php-fpm",
-    "dockerComposeFile": ["../../docker-compose.yml", "../../docker-compose-dev.yml"],
-    "mounts":["source=vscode-extensions-php,target=/var/www/symfony/.vscode-server/extensions,type=volume"]
-
+    "dockerComposeFile": [
+        "../../docker-compose.yml",
+        "../../docker-compose-dev.yml"
+    ],
+    "mounts": [
+        "source=vscode-extensions-php,target=/var/www/symfony/.vscode-server/extensions,type=volume"
+    ]
 }
\ No newline at end of file
diff --git a/website/composer.json b/website/composer.json
index 637b00e..f555810 100644
--- a/website/composer.json
+++ b/website/composer.json
@@ -16,7 +16,7 @@
         "symfony/twig-pack": "*",
         "symfony/yaml": "5.0.*",
         "symfony/asset": "*",
-		"symfony/form" : "*"
+	"symfony/form" : "*"
     },
     "require-dev": {
         "friendsofphp/php-cs-fixer": "^2.16",
diff --git a/website/config/services.yaml b/website/config/services.yaml
index c6ceef1..39e803c 100644
--- a/website/config/services.yaml
+++ b/website/config/services.yaml
@@ -35,4 +35,4 @@ services:
     App\Repository\IOpenDataManager: 
         alias: 'App\Repository\OpenDataManager'
         public: true
-    App\Form\Type\DataSetType: ~
+    App\Form\DataSetType: ~
diff --git a/website/public/css/style.css b/website/public/css/style.css
index e38467b..c934f8f 100644
--- a/website/public/css/style.css
+++ b/website/public/css/style.css
@@ -1102,8 +1102,7 @@ p.logos-partners a:first-of-type img {
 .leaflet-popup-content-wrapper, .leaflet-popup-tip {
   font-family: 'Be Vietnam', sans-serif;
   text-align: center;
-  color: #fff;
-  background-color: #0b155a !important;
+  color: white;
 }
 
 .leaflet-popup-content-wrapper .leaflet-popup-content strong {
@@ -1147,7 +1146,6 @@ p.logos-partners a:first-of-type img {
 .leaflet-popup-content-wrapper #count-info {
   margin: auto;
   color: #fff;
-  background-color: #0b155a;
 }
 
 header.map .nav-item {
@@ -1221,3 +1219,20 @@ header.map .nav-item select:focus {
   background: #0048a9;
   color: white;
 }
+
+.custom-dropdown-item {
+  margin: 0px !important;
+}
+
+.custom-dropdown-item .disabled {
+  cursor: not-allowed !important;
+}
+
+.custom-control-label {
+  font-weight: 300 !important;
+  margin: 0px !important;
+}
+
+.custom-dropdown {
+  cursor: not-allowed;
+}
diff --git a/website/public/css/style.scss b/website/public/css/style.scss
index bbe6120..64a176c 100755
--- a/website/public/css/style.scss
+++ b/website/public/css/style.scss
@@ -1002,8 +1002,7 @@ p.logos-partners {
 .leaflet-popup-content-wrapper, .leaflet-popup-tip {
   font-family: 'Be Vietnam', sans-serif;
   text-align: center;
-  color: #fff;
-  background-color: #0b155a !important;
+  color: white;
 }
 
 .leaflet-popup-content-wrapper {
@@ -1048,7 +1047,6 @@ p.logos-partners {
   #count-info {
     margin: auto;
     color: #fff;
-    background-color: #0b155a;
   }
 }
 
@@ -1125,4 +1123,17 @@ header.map .nav-item {
       color: white;
     }
   }
+}
+.custom-dropdown-item {
+  margin: 0px !important;
+}
+.custom-dropdown-item .disabled {
+  cursor: not-allowed !important;
+}
+.custom-control-label {
+  font-weight: 300 !important;
+  margin: 0px !important;
+}
+.custom-dropdown {
+  cursor: not-allowed;
 }
\ No newline at end of file
diff --git a/website/public/js/cache-handler.js b/website/public/js/cache-handler.js
new file mode 100644
index 0000000..3c1f66c
--- /dev/null
+++ b/website/public/js/cache-handler.js
@@ -0,0 +1,26 @@
+const cache = {}
+export const updateCache = (key, value, cache_ = cache) => {
+  if (typeof value === 'object') {
+    if (!(key in cache_)) {
+      cache_[key] = {}
+    }
+    const keys = Object.keys(value)
+    if (!keys.length) {
+      return undefined
+    }
+    keys.forEach((key_) => {
+      cache_[key][key_] = updateCache(key_, value[key_], cache_[key])
+    })
+  } else if (Array.isArray(value)) {
+    cache_[key] = value.map((item) => {
+      updateCache()
+    })
+  } else {
+    cache_[key] = value
+  }
+  return cache_
+}
+export const getCachedData = (key) => {
+  return cache[key]
+}
+export default cache
diff --git a/website/public/js/zcu-heatmap.js b/website/public/js/zcu-heatmap.js
index 5a0b12f..da1cfa4 100644
--- a/website/public/js/zcu-heatmap.js
+++ b/website/public/js/zcu-heatmap.js
@@ -1,5 +1,6 @@
 /* global L */
 /* global $ */
+
 var mymap
 var heatmapLayer = null
 var marksLayer = null
@@ -10,48 +11,81 @@ var startZoom = 17
 
 var dataSourceRoute
 var currentTime
-var name
-var date
 
 var timer
 var isAnimationRunning = false
 var data = []
 
 var info = []
-var currenInfo = 0
+let currentInfo = 0
+
+var datasetSelected = []
 
-// holds all instances of markers for bind/unbind popup purpose
-// contains: {key:[L.circle,L.pupup]}
-// key: x and y, x + '' + y string
 const globalMarkersHolder = {}
+const datasetColorDict = {}
 
+/* const genColor = (datasetNames) => {
+  datasetNames.forEach((name) => {
+    datasetColorDict[name] =
+  })
+} */
+const fetchByNameDate = async (baseRoute, name, date, currentTime) => {
+  const headers = new Headers()
+  const myRequest = new Request(baseRoute + '/' + name + '/' + date + '/' + currentTime, {
+    method: 'GET',
+    headers: headers
+  })
+  const beforeJson = await fetch(myRequest)
+  return beforeJson.json()
+}
+const fetchDataSourceMarks = async (positionRoute, datasetName) => {
+  const headers = new Headers()
+  const myRequest = new Request(positionRoute + '/' + datasetName, {
+    method: 'GET',
+    headers: headers
+  })
+  const beforeJson = await fetch(myRequest)
+  return beforeJson.json()
+}
 // all marker from which popup was removed
 // contains: {key:[L.circle,L.pupup]}
 // key: x and y, x + '' + y string
 let globalMarkersChanged = {}
 
+const genPopUpControlButtons = (currentPage, numPages, onNextClick, onPreviousClick) => ({
+  previousButton: '<button id="previous-info-btn" class="circle-button" onclick="previousInfo()"></button>',
+  nextButton: '<button id="next-info-btn" onclick="nextInfo()" class="circle-button next"></button>',
+  posInfo: `<div id="count-info">${currentPage} z ${numPages}</div>`
+})
 const genPopUpControls = (controls) => {
-  return `<div class="popup-controls">${controls.reduce((sum, item) => sum + item, '')}</div>`
+  return `<div class="popup-controls">${controls ? controls.reduce((sum, item) => sum + item, '') : ''}</div>`
 }
+const multipleDatasetsPopUp = (sum, currentPos, maxPos, datasetName) => {
+  const header = `<strong>Dataset a počet:</strong><div id="place-info">${datasetName}</div>`
+  const digitInfo = `<div id="number-info"><span id="digit-info">${sum}</span></div>`
+  const { previousButton, nextButton, posInfo } = genPopUpControlButtons(currentPos, maxPos)
+  return `
+  ${header}
+  ${digitInfo}
+  ${genPopUpControls([previousButton, posInfo, nextButton])}
+  `
+}
+const prepareLayerPopUp = (lat, lng, num, className) => L.popup({
+  autoPan: false,
+  className: className
+}).setLatLng([lat / num, lng / num])
+
 const genPopUp = (place, number, sum, currentPos, maxPos) => {
   const header = `<strong>Zařízení a počet:</strong><div id="place-info">${place}</div>`
   const currentNum = `<span id="digit-info">${number}</span>`
   // eslint-disable-next-line eqeqeq
   const sumNum = `<span id="total-info" style="font-size: large">${(sum && (sum != number)) ? '/' + sum : ''}</span>`
   const digitInfo = `<div id="number-info">${currentNum}${sumNum}</div>`
-  let previousButton = '<button id="previous-info-btn" class="circle-button" onclick="previousInfo()"></button>'
-  let nextButton = '<button id="next-info-btn" onclick="nextInfo()" class="circle-button next"></button>'
-  let posInfo = `<div id="count-info">${currentPos} z ${maxPos}</div>`
-
-  if (!sum) {
-    previousButton = ''
-    nextButton = ''
-    posInfo = ''
-  }
+  const { previousButton, nextButton, posInfo } = genPopUpControlButtons(currentPos, maxPos)
   return `
   ${header}
   ${digitInfo}
-  ${genPopUpControls([previousButton, posInfo, nextButton])}
+  ${genPopUpControls(maxPos > 1 ? [previousButton, posInfo, nextButton] : null)}
   `
 }
 /**
@@ -72,64 +106,118 @@ function initMap () {
 
   mymap.on('click', showInfo)
 }
-
+const getInfoLength = () => {
+  const infoKeys = Object.keys(info)
+  if (infoKeys.length === 1) {
+    // return number of records in one dataset (one dataset in area)
+    return info[infoKeys[0]].items.length
+  }
+  // return number of datasets (agregation of all datasets in area)
+  return infoKeys.length
+}
+const getElFromObjectInfo = (position) => {
+  const keys = Object.keys(info)
+  return info[keys[position]]
+}
+const hasInfoMultipleDatasets = () => {
+  return Object.keys(info).length > 1
+}
 function showInfo (e) {
   info = []
-  currenInfo = 0
+  currentInfo = 0
 
   // https://wiki.openstreetmap.org/wiki/Zoom_levels
   // Todo change to variable - it is used in heatmap init
-  var stile = 40075016.686 * Math.cos(startX) / Math.pow(2, mymap.getZoom())
-  var radius = 25 * stile / 256
-
-  var i = 0
-  var lat = 0
-  var lng = 0
-
-  var total = 0
-  data[currentTime].items.forEach(element => {
-    if (e.latlng.distanceTo(new L.LatLng(element.x, element.y)) < radius) {
-      lat += element.x
-      lng += element.y
-      info[i] = { place: element.place, number: element.number }
-      total += parseInt(element.number)
-      i++
-    }
+  const stile = 40075016.686 * Math.cos(startX) / Math.pow(2, mymap.getZoom())
+  const radius = 25 * stile / 256
+
+  let i = 0
+  let lat = 0
+  let lng = 0
+
+  let total = 0
+
+  const datasetsInRadius = {}
+
+  Object.keys(data[currentTime]).forEach((key) => {
+    const namedData = data[currentTime][key]
+    namedData.items.forEach(element => {
+      if (e.latlng.distanceTo(new L.LatLng(element.x, element.y)) < radius) {
+        lat += element.x
+        lng += element.y
+        info[i] = { place: element.place, number: element.number, datasetName: key }
+        total += parseInt(element.number)
+        i++
+        datasetsInRadius[key] = true
+      }
+    })
   })
 
-  if (info.length > 0) {
-    const { place, number } = info[currenInfo]
-    L.popup({
-      autoPan: false
-    })
-      .setLatLng([lat / i, lng / i])
-      .setContent(genPopUp(place, number, total, currenInfo + 1, info.length))
-      .openOn(mymap)
+  // Process info for more then one dataset
 
-    if (info.length === 1) {
+  info = info.reduce((acc, item) => {
+    if (!acc[item.datasetName]) {
+      acc[item.datasetName] = {
+        items: [],
+        number: 0,
+        datasetName: item.datasetName
+      }
+    }
+    acc[item.datasetName].items.push(item)
+    acc[item.datasetName].number += Number(item.number)
+    return acc
+  }, {})
+
+  // There is one dataset
+
+  const numDatasets = Object.keys(datasetsInRadius).length
+
+  if (!numDatasets) { return }
+
+  if (numDatasets === 1) {
+    const infoDict = getElFromObjectInfo(0)
+    const info_ = infoDict.items
+    const { place, number } = info_[currentInfo]
+    prepareLayerPopUp(lat, lng, i, `popup-${infoDict.datasetName}`)
+      .setContent(genPopUp(place, number, total, currentInfo + 1, info_.length))
+      .openOn(mymap)
+    if (info_.length === 1) {
       $('#previous-info-btn').prop('disabled', true)
       $('#next-info-btn').prop('disabled', true)
       $('.popup-controls').hide()
     }
+  } else {
+    const { datasetName, number } = getElFromObjectInfo(currentInfo)
+    prepareLayerPopUp(lat, lng, i, `popup-${datasetName}`)
+      .setContent(multipleDatasetsPopUp(number, currentInfo + 1, getInfoLength(), datasetName))
+      .openOn(mymap)
   }
 }
 
 // eslint-disable-next-line no-unused-vars
 function previousInfo () {
-  currenInfo = (currenInfo + info.length - 1) % info.length
-  displayInfoText()
+  const infoLength = getInfoLength()
+  const previousCurrentInfo = currentInfo
+  currentInfo = (currentInfo + infoLength - 1) % infoLength
+  displayInfoText(previousCurrentInfo)
 }
 
 // eslint-disable-next-line no-unused-vars
 function nextInfo () {
-  currenInfo = (currenInfo + 1) % info.length
-  displayInfoText()
+  const infoLength = getInfoLength()
+  const previousCurrentInfo = currentInfo
+  currentInfo = (currentInfo + 1) % infoLength
+  displayInfoText(previousCurrentInfo)
 }
-
-function displayInfoText () {
-  $('#place-info').html(info[currenInfo].place)
-  $('#digit-info').html(info[currenInfo].number)
-  $('#count-info').html(currenInfo + 1 + ' z ' + info.length)
+function displayInfoText (previousInfoNum) {
+  const previousInfo = hasInfoMultipleDatasets() ? getElFromObjectInfo(previousInfoNum) : getElFromObjectInfo(0).items[previousInfoNum]
+  const info_ = hasInfoMultipleDatasets() ? getElFromObjectInfo(currentInfo) : getElFromObjectInfo(0).items[currentInfo]
+  const infoLength = getInfoLength()
+  $('#place-info').html(info_.place ? info_.place : info_.datasetName)
+  $('#digit-info').html(info_.number)
+  $('#count-info').html(currentInfo + 1 + ' z ' + infoLength)
+  $('.leaflet-popup').removeClass(`popup-${previousInfo.datasetName}`)
+  $('.leaflet-popup').addClass(`popup-${info_.datasetName}`)
 }
 
 // eslint-disable-next-line no-unused-vars
@@ -178,7 +266,7 @@ function next () {
   updateHeaderControls()
   changeUrl()
 }
-
+const typeUrlReducer = (accumulator, currentValue) => accumulator + currentValue
 /**
  * Change browser url based on animation step
  */
@@ -186,7 +274,7 @@ function changeUrl () {
   window.history.pushState(
     '',
     document.title,
-    window.location.origin + window.location.pathname + `?data_set[date]=${$('#date').val()}&data_set[time]=${currentTime}&data_set[type]=${$('#type').children('option:selected').val()}`
+    window.location.origin + window.location.pathname + `?date=${$('#date').val()}&time=${currentTime}${datasetSelected.reduce((acc, current) => acc + '&type[]=' + current, '')}`
   )
 }
 
@@ -205,32 +293,32 @@ function setTimeline () {
  * @param {string} positionsRoute  route to dataset postitions source
  */
 // eslint-disable-next-line no-unused-vars
-function loadCurrentTimeHeatmap (opendataRoute, positionsRoute) {
+async function loadCurrentTimeHeatmap (opendataRoute, positionsRoute) {
   dataSourceRoute = opendataRoute
   data = []
-
-  name = $('#type').children('option:selected').val()
-  date = $('#date').val()
+  const dataSourceMarks = {}
+  const allPromises = []
+  const date = $('#date').val()
   currentTime = parseInt($('#time').children('option:selected').val())
   setTimeline()
-  $.ajax({
-    type: 'POST',
-    url: positionsRoute + '/' + name,
-    success: function (result) {
-      drawDataSourceMarks(result)
-      $.ajax({
-        type: 'POST',
-        url: dataSourceRoute + '/' + name + '/' + date + '/' + currentTime,
-        success: function (result) {
-          data[currentTime] = result
-          drawHeatmap(data[currentTime])
-        }
-      })
-    }
+  data[currentTime] = {}
+  const dataSelectedHandler = async (datasetName) => {
+    const marks = await fetchDataSourceMarks(positionsRoute, datasetName)
+    const datasetData = await fetchByNameDate(dataSourceRoute, datasetName, date, currentTime)
+    dataSourceMarks[datasetName] = marks
+    data[currentTime][datasetName] = datasetData
+  }
+  await datasetSelected.forEach((datasetName) => {
+    allPromises.push(dataSelectedHandler(datasetName))
   })
-
-  preload(currentTime, 1)
-  preload(currentTime, -1)
+  Promise.all(allPromises).then(
+    () => {
+      drawDataSourceMarks(dataSourceMarks)
+      drawHeatmap(data[currentTime])
+      preload(currentTime, 1, date)
+      preload(currentTime, -1, date)
+    }
+  )
 }
 
 function drawDataSourceMarks (data) {
@@ -238,72 +326,76 @@ function drawDataSourceMarks (data) {
     L.removeLayer(marksLayer)
   }
   marksLayer = L.layerGroup()
-  for (var key in data) {
-    const { x, y, name } = data[key]
-    const pop =
-      L.popup({ autoPan: false })
-        .setLatLng([x, y])
-        .setContent(genPopUp(name, 0, 0, 1, 1))
-    const newCircle =
-      L.circle([x, y], { radius: 2, fillOpacity: 0.8, color: '#004fb3', fillColor: '#004fb3', bubblingMouseEvents: true })
-        .bindPopup(pop)
-    globalMarkersHolder[x + '' + y] = [newCircle, pop] // add new marker to global holders
-    marksLayer.addLayer(
-      newCircle
-    )
-  }
+  Object.keys(data).forEach((key_) => {
+    for (var key in data[key_]) {
+      const { x, y, name } = data[key_][key]
+      const pop =
+          prepareLayerPopUp(x, y, 1, `popup-${key_}`)
+            .setContent(genPopUp(name, 0, 0, 1, 1))
+      const newCircle =
+        L.circle([x, y], { radius: 2, fillOpacity: 0.8, color: '#004fb3', fillColor: '#004fb3', bubblingMouseEvents: true })
+          .bindPopup(pop)
+      globalMarkersHolder[x + '' + y] = [newCircle, pop] // add new marker to global holders
+      marksLayer.addLayer(
+        newCircle
+      )
+    }
+  })
 
   marksLayer.setZIndex(-1).addTo(mymap)
 }
 
-function preload (time, change) {
-  var ntime = time + change
-  if (ntime >= 0 && ntime <= 23) {
-    $.ajax({
-      type: 'POST',
-      url: dataSourceRoute + '/' + name + '/' + date + '/' + ntime,
-      success: function (result) {
-        data[ntime] = result
-        preload(ntime, change)
-      }
-    })
+async function preload (time, change, date) {
+  for (let nTime = time + change; nTime >= 0 && nTime <= 23; nTime = nTime + change) {
+    if (!data[nTime]) {
+      data[nTime] = {}
+      datasetSelected.forEach(async (datasetName) => {
+        data[nTime][datasetName] = await fetchByNameDate(dataSourceRoute, datasetName, date, nTime)
+      })
+    }
   }
 }
 
-function drawHeatmap (data) {
+function drawHeatmap (dataRaw) {
   // Todo still switched
-  if (data.items != null) {
+  const dataDict = dataRaw
+  const mergedPoints = []
+  let max = 0
+  Object.keys(dataDict).forEach((key) => {
+    const data = dataDict[key]
+    max = Math.max(max, data.max)
+    if (data != null) {
     // Bind back popups for markers (we dont know if there is any data for this marker or not)
-    if (Object.keys(globalMarkersChanged).length) {
-      Object.keys(globalMarkersChanged).forEach(function (key) {
-        globalMarkersChanged[key][0].bindPopup(globalMarkersChanged[key][1])
-      })
-      globalMarkersChanged = {}
-    }
-    const points = data.items.map((point) => {
-      const { x, y, number } = point
-      const key = x + '' + y
-      const holder = globalMarkersHolder[key]
-      if (!globalMarkersChanged[key] && number) {
+      if (Object.keys(globalMarkersChanged).length) {
+        Object.keys(globalMarkersChanged).forEach(function (key) {
+          globalMarkersChanged[key][0].bindPopup(globalMarkersChanged[key][1])
+        })
+        globalMarkersChanged = {}
+      }
+      const points = data.items.map((point) => {
+        const { x, y, number } = point
+        const key = x + '' + y
+        const holder = globalMarkersHolder[key]
+        if (!globalMarkersChanged[key] && number) {
         // There is data for this marker => unbind popup with zero value
-        holder[0] = holder[0].unbindPopup()
-        globalMarkersChanged[key] = holder
+          holder[0] = holder[0].unbindPopup()
+          globalMarkersChanged[key] = holder
+        }
+        return [x, y, number]
+      })
+      mergedPoints.push(...points)
+    } else {
+      if (heatmapLayer != null) {
+        mymap.removeLayer(heatmapLayer)
       }
-      return [x, y, number]
-    })
-    if (heatmapLayer != null) {
-      mymap.removeLayer(heatmapLayer)
-    }
-    heatmapLayer = L.heatLayer(points, { max: data.max, minOpacity: 0.5, radius: 35, blur: 30 }).addTo(mymap)
-  } else {
-    if (heatmapLayer != null) {
-      mymap.removeLayer(heatmapLayer)
     }
+  })
+  if (heatmapLayer != null) {
+    mymap.removeLayer(heatmapLayer)
+  }
+  if (mergedPoints.length) {
+    heatmapLayer = L.heatLayer(mergedPoints, { max: max, minOpacity: 0.5, radius: 35, blur: 30 }).addTo(mymap)
   }
-
-  // var heat_01 = ...
-  // on background map.addLayer(heat_01) -> map.removeLayer(heat_01);
-  // $(.leaflet-heatmap-layer).css('opacity', 'value');
 }
 
 /**
@@ -322,26 +414,22 @@ function checkDataSetsAvailability (route) {
   })
 }
 
-var allOptionsDisabled = false
-
 function updateAvailableDataSets (available) {
-  var isOptionEnabled = true
-  $('#type > option').each(function () {
-    if ((this.value in available) === false) {
-      $(this).prop('disabled', true)
-      $(this).prop('selected', false)
+  let leastOneOptionEnabled = false
+  // datasetSelected = []
+  $('#dataset-dropdown .dropdown-item').each(function () {
+    const input = $(this).find('input')
+    const inputVal = input[0].value
+    if (!(inputVal in available)) {
+      $(this).addClass('disabled')
+      $(input).prop('checked', false)
     } else {
-      $(this).prop('disabled', false)
-      if (allOptionsDisabled) {
-        $(this).prop('selected', true)
-        allOptionsDisabled = false
-      }
-      isOptionEnabled = false
+      leastOneOptionEnabled = true
+      $(this).removeClass('disabled')
     }
   })
-  allOptionsDisabled = isOptionEnabled
 
-  $('#submit-btn').prop('disabled', isOptionEnabled)
+  $('#submit-btn').prop('disabled', !leastOneOptionEnabled)
 }
 
 function formatDate (date) {
@@ -403,3 +491,17 @@ function openDatepicker () {
 
   $('#date').datepicker('show')
 }
+function onDocumentReady () {
+  $('#dataset-dropdown').on('click', function (e) {
+    e.stopPropagation()
+  })
+  datasetSelected = []
+  $('#dataset-dropdown .dropdown-item').each(function () {
+    const input = $(this).find('input')
+    const inputVal = input[0].value
+    if (input[0].checked) {
+      datasetSelected.push(inputVal)
+    }
+  })
+  $('#submit-btn').prop('name', '')
+}
diff --git a/website/src/Controller/HeatmapController.php b/website/src/Controller/HeatmapController.php
index c25d7f5..9bac50b 100644
--- a/website/src/Controller/HeatmapController.php
+++ b/website/src/Controller/HeatmapController.php
@@ -2,8 +2,9 @@
 
 namespace App\Controller;
 
+use App\Utils\Utils;
 use App\Entity\DataSet;
-use App\Form\DataSetType;
+use App\Form\DatasetFormBuilder;
 use App\Repository\IOpenDataManager;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Annotation\Route;
@@ -19,20 +20,33 @@ class HeatmapController extends AbstractController {
      */
     public function index(Request $request, IOpenDataManager $manager) {
         $dataSet = new DataSet();
+        $datasetFormBuilder = new DatasetFormBuilder($manager);
+        $formBuilder = $datasetFormBuilder->getBuilder();
 
-        $form = $this->createForm(DataSetType::class, $dataSet);
-        $form->handleRequest($request);
-
+        $form = $formBuilder->getForm();
+        $form->submit($request->query->all());
         $isSubmitted = $form->isSubmitted();
         if ($isSubmitted) {
             $dataSet = $form->getData();
-            // Chceck whether the data is valid i.e. such a collection exists
-            if (false == $manager->isCollectionAvailable($dataSet->getType(), $dataSet->getDate())) {
+            //check availability
+            $notExist = false;
+            if ($dataSet->getType() && count($dataSet->getType()) > 0) {
+                foreach ($dataSet->getType() as $value) {
+                    if (!$manager->isCollectionAvailable($value, $dataSet->getDate())) {
+                        $notExist = true;
+                        break;
+                    }
+                }
+            } else {
+                $notExist = true;
+            }
+            if ($notExist) {
                 // Not? Populate form with empty data
-                $dataSet = new DataSet();
+                $formBuilder = $datasetFormBuilder->getBuilder();
+                $form = $formBuilder->getForm();
             }
-            $form = $this->createForm(DataSetType::class, $dataSet);
         }
+        //ziskej barvy o datasetech a namapuj je na jejich jmena
 
         return $this->render(
             'heatmap.html.twig',
@@ -40,6 +54,7 @@ class HeatmapController extends AbstractController {
                 'form' => $form->createView(),
                 'submitted' => $isSubmitted,
                 'data_to_display' => $dataSet,
+                'dataset_colors' => Utils::prepareDatasetsColors($manager->getAvailableCollections()),
             ]
         );
     }
diff --git a/website/src/Form/DatasetFormBuilder.php b/website/src/Form/DatasetFormBuilder.php
new file mode 100644
index 0000000..2ab8001
--- /dev/null
+++ b/website/src/Form/DatasetFormBuilder.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace App\Form;
+
+use App\Utils\Utils;
+use App\Entity\DataSet;
+use Symfony\Component\Form\Forms;
+use App\Repository\IOpenDataManager;
+use Symfony\Component\Form\Extension\Core\Type\FormType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+
+class DatasetFormBuilder {
+    /**
+     * @var IOpenDataManager autowired connection to database
+     */
+    private $manager;
+
+    /**
+     * @param IOpenDataManager autowired connection to database
+     */
+    public function __construct(IOpenDataManager $manager) {
+        $this->manager = $manager;
+    }
+
+    public function getBuilder() {
+        $dataSet = new DataSet();
+
+        return Forms::createFormFactoryBuilder()->getFormFactory()->createNamedBuilder('', FormType::class, $dataSet)
+            ->add('date', TextType::class)
+            ->add('time', ChoiceType::class, [
+                'choices' => [
+                    '0:00-1:00' => 0,
+                    '1:00-2:00' => 1,
+                    '2:00-3:00' => 2,
+                    '3:00-4:00' => 3,
+                    '4:00-5:00' => 4,
+                    '5:00-6:00' => 5,
+                    '6:00-7:00' => 6,
+                    '7:00-8:00' => 7,
+                    '8:00-9:00' => 8,
+                    '9:00-10:00' => 9,
+                    '10:00-11:00' => 10,
+                    '11:00-12:00' => 11,
+                    '12:00-13:00' => 12,
+                    '13:00-14:00' => 13,
+                    '14:00-15:00' => 14,
+                    '15:00-16:00' => 15,
+                    '16:00-17:00' => 16,
+                    '17:00-18:00' => 17,
+                    '18:00-19:00' => 18,
+                    '19:00-20:00' => 19,
+                    '20:00-21:00' => 20,
+                    '21:00-22:00' => 21,
+                    '22:00-23:00' => 22,
+                    '23:00-0:00' => 23,
+                ],
+            ])->add('type', ChoiceType::class, [
+                'choices' => Utils::prepareDatasetsNames($this->manager->getAvailableCollections()),
+                'multiple' => true,
+                'expanded' => true,
+            ])
+            ->add('submit', SubmitType::class);
+    }
+}
diff --git a/website/src/Repository/OpenDataManager.php b/website/src/Repository/OpenDataManager.php
index e512dd9..1ef0be4 100644
--- a/website/src/Repository/OpenDataManager.php
+++ b/website/src/Repository/OpenDataManager.php
@@ -46,7 +46,7 @@ class OpenDataManager implements IOpenDataManager {
     }
 
     public function getAvailableCollections() {
-        $openData = $this->manager->executeQuery('open-data-db.DATASETS', new Query([], ['projection' => ['key-name' => 1, 'display-name' => 1, '_id' => 0]]));
+        $openData = $this->manager->executeQuery('open-data-db.DATASETS', new Query([], ['projection' => ['key-name' => 1, 'display-name' => 1, 'display-color' => 1, '_id' => 0]]));
 
         // Converts result to php array
         $openData->setTypeMap([
diff --git a/website/src/Utils/Utils.php b/website/src/Utils/Utils.php
index a26d3e8..252dd7f 100644
--- a/website/src/Utils/Utils.php
+++ b/website/src/Utils/Utils.php
@@ -17,7 +17,6 @@ class Utils {
     public static function prepareDatasetsNames($datasets) {
         $names = [];
 
-        $index = 0;
         foreach ($datasets as $key => $value) {
             if (false == array_key_exists($value['key-name'], $names)) {
                 $names[$value['display-name']] = $value['key-name'];
@@ -26,4 +25,15 @@ class Utils {
 
         return $names;
     }
+
+    public static function prepareDatasetsColors($datasets) {
+        $colors = [];
+        foreach ($datasets as $key => $value) {
+            if (false == array_key_exists($value['key-name'], $colors)) {
+                $colors[$value['key-name']] = $value['display-color'];
+            }
+        }
+
+        return $colors;
+    }
 }
diff --git a/website/symfony.code-workspace b/website/symfony.code-workspace
index 8f39029..13b745e 100644
--- a/website/symfony.code-workspace
+++ b/website/symfony.code-workspace
@@ -15,7 +15,7 @@
 		"eslint.codeActionsOnSave.mode": "all",
 		"editor.codeActionsOnSave": {
 			"source.fixAll": true
-		}
+		},
 	},
 	"extensions": {
 		"recommendations": [
diff --git a/website/templates/base.html.twig b/website/templates/base.html.twig
index 65786c9..ba81c40 100644
--- a/website/templates/base.html.twig
+++ b/website/templates/base.html.twig
@@ -22,5 +22,6 @@
     <body{% block bodyClass %}{% endblock %}>
         {% block body %}{% endblock %}
         {% block javascripts %}{% endblock %}
+        {% block style %}{% endblock %}
     </body>
 </html>
diff --git a/website/templates/heatmap.html.twig b/website/templates/heatmap.html.twig
index 807693b..8ceafd1 100644
--- a/website/templates/heatmap.html.twig
+++ b/website/templates/heatmap.html.twig
@@ -51,27 +51,39 @@
                   'id' : 'time',
                   'attr' : {
                     'class' : 'custom-select'
-                  }
+                  },
                 })
               }}             
             </li>
 
             <li class="nav-item">
-              {{ form_label(form.type, 'Vyberte datovou sadu') }}
-              {{
-                form_widget(form.type, {
-                  'id' : 'type',
-                  'attr' : {
-                    'class' : 'custom-select'
-                  }
-                })
-              }}  
+              <div class="dropdown">
+              {# {{ form_label(form.type, 'Vyberte datovou sadu') }} #}
+                <button class="btn dropdown-toggle text-white font-weight-bold" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                  Vyberte datovou sadu
+              </button>
+              <div id="dataset-dropdown" class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+              {% for oneType in form.type.children %}
+              <label class="dropdown-item custom-dropdown-item">
+              <div class="custom-control custom-checkbox custom-checkbox-{{oneType.vars.value}}">
+              {{ form_widget(oneType, {
+                attr: {'class': "custom-control-input custom-control-input-#{oneType.vars.value}"}
+              }) }}
+              {{ form_label(oneType, null, {
+                'label_attr': {'class': 'custom-control-label'}
+                }) }}
+              </div>
+              </label>
+              {% endfor %}
+              </div>
+              </div>  
             </li>
 
             <li class="nav-item">
               {{
                 form_widget(form.submit,
                 {
+                  'name' : '',
                   'label' : 'Potvrdit výběr',
                   'type' : 'submit',
                   'id' : 'submit-btn',
@@ -171,15 +183,46 @@
     <script>
         initDatepicker("{{ path('dates') }}");
         initLocationsMenu();
-        initMap();
-        checkDataSetsAvailability("{{ path('available') }}")
-        {% if submitted %}
-        loadCurrentTimeHeatmap("{{ path('opendata') }}", "{{ path('positions') }}");
-        {% endif %}
-
+        initMap();        
+        $( document ).ready(function() {
+          onDocumentReady();
+          checkDataSetsAvailability("{{ path('available') }}")
+          {% if submitted %}
+          loadCurrentTimeHeatmap("{{ path('opendata') }}", "{{ path('positions') }}");
+          {% endif %}
+        });
         $(window).resize(function() {
           initLocationsMenu();
         });
   </script>
 
+{% endblock %}
+
+{% block style %}
+  <style>
+    {% for key, item in dataset_colors %}
+      .custom-control-input-{{key}}:focus~.custom-control-label::before {
+          border-color: {{item}} !important;
+          box-shadow: 0 0 0 0.2rem rgba(192,192,192, 0.4) !important;
+      }
+
+      .custom-control-input-{{key}}:checked~.custom-control-label::before {
+          border-color: {{item}} !important;
+          background-color: {{item}} !important;
+      }
+
+      .custom-control-input-{{key}}:focus:not(:checked)~.custom-control-label::before {
+          border-color: {{item}} !important;
+      }
+
+      .custom-control-input-{{key}}:not(:disabled):active~.custom-control-label::before {
+          background-color: {{item}} !important;
+          border-color: {{item}} !important;
+      }
+
+      .popup-{{key}} > .leaflet-popup-content-wrapper, .popup-{{key}} .leaflet-popup-tip{
+        background-color: {{item}} !important;
+      }
+    {% endfor %}
+  </style>
 {% endblock %}
\ No newline at end of file
diff --git a/website/templates/index.html.twig b/website/templates/index.html.twig
index 99bb5a6..31cd890 100644
--- a/website/templates/index.html.twig
+++ b/website/templates/index.html.twig
@@ -45,7 +45,7 @@
           </li>
           <li class="nav-item button">
             <h2>
-              <a href="/heatmap/?data_set[date]=2019-04-11&amp;data_set[time]=9&amp;data_set[type]=JIS" class="nav-link">Zobrazit heatmapu</a>
+              <a href="/heatmap/?date=2019-04-11&amp;time=9&amp;type[]=JIS" class="nav-link">Zobrazit heatmapu</a>
             </h2>
           </li>
         </ul>
@@ -62,7 +62,7 @@
             <h3>Život na ZČU během dne</h3>
             <hr>
             <p>Zobrazte si heatmapu života univerzity hodinu po hodině během jednoho dne. Od ranního probouzení, kdy studenti a&nbsp;zaměstnanci přicházejí do laboratoří, přes polední špičku v&nbsp;menze až do samotného večera, kdy končí poslední z&nbsp;přednášek a&nbsp;cvičení.</p>
-            <a href="/heatmap/?data_set[date]=2019-04-11&amp;data_set[time]=9&amp;data_set[type]=JIS" class="btn btn-primary" role="button">Vyzkoušet heatmapu</a>
+            <a href="/heatmap/?date=2019-04-11&amp;time=9&amp;type[]=JIS" class="btn btn-primary" role="button">Vyzkoušet heatmapu</a>
           </div>
         </div>
 
diff --git a/website/tests/Controller/HeatmapControllerTest.php b/website/tests/Controller/HeatmapControllerTest.php
index 40df004..4147f4e 100644
--- a/website/tests/Controller/HeatmapControllerTest.php
+++ b/website/tests/Controller/HeatmapControllerTest.php
@@ -9,24 +9,26 @@ class HatmapControllerTest extends WebTestCase {
     public function testPageLoad() {
         $client = static::createClient();
         $client->request('GET', '/heatmap');
+
         $this->assertTrue($client->getResponse()->isSuccessful());
     }
 
     public function testPageLoadWithData() {
         $client = static::createClient();
-        $client->request('GET', '/heatmap?data_set[date]=2019-04-11&data_set[time]=12&data_set[type]=JIS');
+        $client->request('GET', '/heatmap?date=2019-04-11&time=12&type[]=JIS');
+        
         $this->assertTrue($client->getResponse()->isSuccessful());
     }
 
     public function testPageLoadWithInvalidData() {
         $client = static::createClient();
-        $client->request('GET', '/heatmap?data_set[date]=2019-04-11&data_set[time]=12&data_set[type]=NIC');
+        $client->request('GET', '/heatmap?date=2019-04-11&time=12&type[]=NIC');
         $this->assertTrue($client->getResponse()->isSuccessful());
 
-        $client->request('GET', '/heatmap?data_set[date]=2019-04-11&data_set[time]=25&data_set[type]=KOLOBEZKY');
+        $client->request('GET', '/heatmap?date=2019-04-11&time=25&type[]=KOLOBEZKY');
         $this->assertTrue($client->getResponse()->isSuccessful());
 
-        $client->request('GET', '/heatmap?data_set[date]=11-04-2019&data_set[time]=12&data_set[type]=KOLOBEZKY');
+        $client->request('GET', '/heatmap?date=11-04-2019&time=12&type[]=KOLOBEZKY');
         $this->assertTrue($client->getResponse()->isSuccessful());
     }
 
@@ -39,9 +41,9 @@ class HatmapControllerTest extends WebTestCase {
 
         $crawler = $client->submitForm('Potvrdit výběr', 
         [
-            'data_set[date]' => '2019-04-11',
-            'data_set[time]' => '0',
-            'data_set[type]' => 'JIS'
+            'date' => '2019-04-11',
+            'time' => '0',
+            'type[1]' => 'JIS'
         ]);
         $this->assertTrue($client->getResponse()->isSuccessful());
     }
@@ -52,9 +54,9 @@ class HatmapControllerTest extends WebTestCase {
 
         $crawler = $client->submitForm('Potvrdit výběr', 
         [
-            'data_set[date]' => '11-04-2019',
-            'data_set[time]' => '0',
-            'data_set[type]' => 'JIS'
+            'date' => '11-04-2019',
+            'time' => '0',
+            'type[1]' => 'JIS'
         ]);
         $this->assertTrue($client->getResponse()->isSuccessful());
     }
