Revize 1774c06d
Přidáno uživatelem Tomáš Ballák před téměř 4 roky(ů)
website/public/js/zcu-heatmap.js | ||
---|---|---|
11 | 11 |
|
12 | 12 |
var dataSourceRoute |
13 | 13 |
let positionsSourceRoute |
14 |
var currentTime |
|
14 |
|
|
15 |
let currentTime |
|
16 |
|
|
17 |
let currentDate |
|
15 | 18 |
|
16 | 19 |
var timer |
17 | 20 |
var isAnimationRunning = false |
... | ... | |
23 | 26 |
const datasetDictNameDisplayName = {} |
24 | 27 |
var datasetSelected = [] |
25 | 28 |
|
29 |
// data only for one day |
|
30 |
let lockedDay = false |
|
31 |
|
|
32 |
let loading = 0 |
|
33 |
|
|
34 |
// marks for all datasets |
|
35 |
const dataSourceMarks = {} |
|
36 |
|
|
26 | 37 |
const globalMarkersHolder = {} |
27 | 38 |
// all marker from which popup was removed |
28 | 39 |
// contains: {key:[L.circle,L.pupup]} |
29 | 40 |
// key: x and y, x + '' + y string |
30 | 41 |
let globalMarkersChanged = {} |
31 | 42 |
|
43 |
const loadingY = () => { |
|
44 |
loading++ |
|
45 |
} |
|
46 |
const loadingN = () => { |
|
47 |
loading-- |
|
48 |
} |
|
49 |
|
|
50 |
const changeCurrentTime = (time = null) => { |
|
51 |
if (time !== null) { |
|
52 |
currentTime = time |
|
53 |
} else { |
|
54 |
$('#dropdown-time input[type="radio"]:checked').each(function () { |
|
55 |
currentTime = parseInt($(this).val()) |
|
56 |
}) |
|
57 |
} |
|
58 |
} |
|
59 |
|
|
60 |
const changeCurrentDate = (date = null) => { |
|
61 |
if (date) { |
|
62 |
currentDate = new Date(date) |
|
63 |
} else { |
|
64 |
currentDate = new Date($('#date').val()) |
|
65 |
} |
|
66 |
$('#player-date span').html(`${currentDate.getDate()}. ${currentDate.getMonth() + 1}. ${currentDate.getFullYear()}`) |
|
67 |
$('#date').val(currentDateToString()) |
|
68 |
data = [] |
|
69 |
} |
|
70 |
const currentDayToString = () => { |
|
71 |
const day = currentDate.getDate() |
|
72 |
return day > 9 ? `${day}` : `0${day}` |
|
73 |
} |
|
74 |
const currentMonthToString = () => { |
|
75 |
const month = currentDate.getMonth() + 1 |
|
76 |
return month > 9 ? `${month}` : `0${month}` |
|
77 |
} |
|
78 |
const currentDateToString = () => `${currentDate.getFullYear()}-${currentMonthToString()}-${currentDayToString()}` |
|
79 |
const addDayToCurrentDate = (day) => { |
|
80 |
currentDate.setDate(currentDate.getDate() + day) |
|
81 |
changeCurrentDate(currentDate) |
|
82 |
} |
|
83 |
const toggleDayLock = () => { |
|
84 |
lockedDay = !lockedDay |
|
85 |
$('#player-date').toggleClass('lock') |
|
86 |
} |
|
32 | 87 |
|
33 | 88 |
const fetchByNameDate = async (baseRoute, name, date, currentTime) => { |
34 | 89 |
const headers = new Headers() |
... | ... | |
40 | 95 |
return beforeJson.json() |
41 | 96 |
} |
42 | 97 |
|
43 |
|
|
44 | 98 |
const fetchDataSourceMarks = async (positionRoute, datasetName) => { |
45 | 99 |
const headers = new Headers() |
46 | 100 |
const myRequest = new Request(positionRoute + '/' + datasetName, { |
... | ... | |
51 | 105 |
return beforeJson.json() |
52 | 106 |
} |
53 | 107 |
|
54 |
|
|
55 | 108 |
const genPopUpControlButtons = (currentPage, numPages, onNextClick, onPreviousClick) => ({ |
56 | 109 |
previousButton: '<button id="previous-info-btn" class="circle-button" onclick="previousInfo()"></button>', |
57 | 110 |
nextButton: '<button id="next-info-btn" class="circle-button next" onclick="nextInfo()"></button>', |
58 | 111 |
posInfo: `<div id="count-info">${currentPage} z ${numPages}</div>` |
59 | 112 |
}) |
60 | 113 |
|
61 |
|
|
62 | 114 |
const genPopUpControls = (controls) => { |
63 | 115 |
return `<div class="popup-controls">${controls ? controls.reduce((sum, item) => sum + item, '') : ''}</div>` |
64 | 116 |
} |
65 | 117 |
|
66 |
|
|
67 | 118 |
const genMultipleDatasetsPopUp = (sum, currentPos, maxPos, datasetName) => { |
68 | 119 |
const popupHeader = `<strong id="dataset-info">${datasetName}</strong>` |
69 | 120 |
const popupData = `<div id="number-info"><span id="digit-info">${sum}</span></div>` |
... | ... | |
76 | 127 |
` |
77 | 128 |
} |
78 | 129 |
|
79 |
|
|
80 | 130 |
const prepareLayerPopUp = (lat, lng, num, className) => L.popup({ |
81 | 131 |
autoPan: false, |
82 | 132 |
className: className |
83 | 133 |
}).setLatLng([lat / num, lng / num]) |
84 | 134 |
|
85 |
|
|
86 | 135 |
const genPopUp = (datasetName, place, count, sum, currentPos, maxPos) => { |
87 | 136 |
const popupHeader = ` |
88 | 137 |
<strong>${datasetName}</strong> |
... | ... | |
101 | 150 |
` |
102 | 151 |
} |
103 | 152 |
|
104 |
|
|
105 | 153 |
const onCheckboxClicked = async (checkbox) => { |
106 | 154 |
if ($(checkbox).prop('checked')) { |
107 | 155 |
loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute) |
108 | 156 |
changeUrl() |
109 |
} |
|
110 |
else { |
|
157 |
} else { |
|
111 | 158 |
loadCheckboxDatasetNameData() |
112 | 159 |
|
113 | 160 |
data.forEach((item, index) => { |
... | ... | |
123 | 170 |
} |
124 | 171 |
} |
125 | 172 |
|
126 |
|
|
127 | 173 |
const debounce = (func, delay) => { |
128 | 174 |
let inDebounce |
129 | 175 |
return function () { |
... | ... | |
134 | 180 |
} |
135 | 181 |
} |
136 | 182 |
|
137 |
|
|
138 | 183 |
const onValueChangeRegister = () => { |
139 | 184 |
$('#date').change(function () { |
140 |
data = []
|
|
185 |
changeCurrentDate($(this).val())
|
|
141 | 186 |
loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute) |
142 |
const date = new Date($(this).val()) |
|
143 |
$('#player-date').html(`${date.getDate()}. ${date.getMonth() + 1}. ${date.getFullYear()}`) |
|
144 | 187 |
changeUrl() |
145 | 188 |
}) |
146 | 189 |
|
147 | 190 |
$('#dropdown-time input[type="radio"]').each(function () { |
148 | 191 |
$(this).change(function () { |
149 |
currentTime = parseInt($(this).val()) |
|
150 |
updateHeaderControls() |
|
151 |
setTimeline() |
|
192 |
changeHour(parseInt($(this).val())) |
|
152 | 193 |
drawHeatmap(data[currentTime]) |
153 |
changeUrl() |
|
154 | 194 |
}) |
155 | 195 |
}) |
156 | 196 |
|
... | ... | |
161 | 201 |
}) |
162 | 202 |
} |
163 | 203 |
|
164 |
|
|
165 | 204 |
/** |
166 | 205 |
* Initialize leaflet map on start position which can be default or set based on user action |
167 | 206 |
*/ |
... | ... | |
181 | 220 |
mymap.on('click', showInfo) |
182 | 221 |
} |
183 | 222 |
|
184 |
|
|
185 | 223 |
const getInfoLength = () => { |
186 | 224 |
const infoKeys = Object.keys(info) |
187 | 225 |
if (infoKeys.length === 1) { |
... | ... | |
192 | 230 |
return infoKeys.length |
193 | 231 |
} |
194 | 232 |
|
195 |
|
|
196 | 233 |
const getElFromObjectInfo = (position) => { |
197 | 234 |
const keys = Object.keys(info) |
198 | 235 |
return info[keys[position]] |
199 | 236 |
} |
200 | 237 |
|
201 |
|
|
202 | 238 |
const hasInfoMultipleDatasets = () => { |
203 | 239 |
return Object.keys(info).length > 1 |
204 | 240 |
} |
205 | 241 |
|
206 |
|
|
207 | 242 |
function showInfo (e) { |
208 | 243 |
info = [] |
209 | 244 |
currentInfo = 0 |
... | ... | |
272 | 307 |
$('#next-info-btn').prop('disabled', true) |
273 | 308 |
$('.popup-controls').hide() |
274 | 309 |
} |
275 |
} |
|
276 |
else { |
|
310 |
} else { |
|
277 | 311 |
const { datasetName, number } = getElFromObjectInfo(currentInfo) |
278 | 312 |
|
279 | 313 |
prepareLayerPopUp(lat, lng, i, `popup-${datasetName}`) |
... | ... | |
282 | 316 |
} |
283 | 317 |
} |
284 | 318 |
|
285 |
|
|
286 | 319 |
// eslint-disable-next-line no-unused-vars |
287 | 320 |
function previousInfo () { |
288 | 321 |
const infoLength = getInfoLength() |
... | ... | |
292 | 325 |
displayInfoText(previousCurrentInfo) |
293 | 326 |
} |
294 | 327 |
|
295 |
|
|
296 | 328 |
// eslint-disable-next-line no-unused-vars |
297 | 329 |
function nextInfo () { |
298 | 330 |
const infoLength = getInfoLength() |
... | ... | |
302 | 334 |
displayInfoText(previousCurrentInfo) |
303 | 335 |
} |
304 | 336 |
|
305 |
|
|
306 | 337 |
function displayInfoText (previousInfoNum) { |
307 | 338 |
const previousInfo = hasInfoMultipleDatasets() ? getElFromObjectInfo(previousInfoNum) : getElFromObjectInfo(0).items[previousInfoNum] |
308 | 339 |
const info_ = hasInfoMultipleDatasets() ? getElFromObjectInfo(currentInfo) : getElFromObjectInfo(0).items[currentInfo] |
... | ... | |
312 | 343 |
if (datasetInfo) { |
313 | 344 |
$(datasetInfo).html(datasetDictNameDisplayName[info_.datasetName]) |
314 | 345 |
} |
315 |
|
|
346 |
|
|
316 | 347 |
$('#place-info').html(info_.place ? info_.place : info_.datasetName) |
317 | 348 |
$('#digit-info').html(info_.number) |
318 | 349 |
$('#count-info').html(currentInfo + 1 + ' z ' + infoLength) |
... | ... | |
321 | 352 |
$('.leaflet-popup').addClass(`popup-${info_.datasetName}`) |
322 | 353 |
} |
323 | 354 |
|
324 |
|
|
325 | 355 |
// eslint-disable-next-line no-unused-vars |
326 | 356 |
function setMapView (latitude, longitude, zoom) { |
327 | 357 |
localStorage.setItem('lat', latitude) |
... | ... | |
330 | 360 |
mymap.setView([latitude, longitude], zoom) |
331 | 361 |
} |
332 | 362 |
|
333 |
|
|
334 | 363 |
/** |
335 | 364 |
* Change animation start from playing to stopped or the other way round |
336 | 365 |
*/ |
... | ... | |
340 | 369 |
|
341 | 370 |
if (isAnimationRunning) { |
342 | 371 |
$('#play-pause').attr('class', 'pause') |
343 |
timer = setInterval(function() { next() }, 800) |
|
344 |
} |
|
345 |
else { |
|
372 |
timer = setInterval(function () { next() }, 800) |
|
373 |
} else { |
|
346 | 374 |
clearTimeout(timer) |
347 | 375 |
$('#play-pause').attr('class', 'play') |
348 | 376 |
} |
349 | 377 |
} |
350 | 378 |
|
351 |
|
|
352 | 379 |
// eslint-disable-next-line no-unused-vars |
353 |
function previous () { |
|
380 |
async function previous () { |
|
381 |
if (loading) { |
|
382 |
return |
|
383 |
} |
|
354 | 384 |
currentTime = (currentTime + 23) % 24 |
355 |
drawHeatmap(data[currentTime]) |
|
356 |
setTimeline() |
|
385 |
changeHour(currentTime) |
|
357 | 386 |
mymap.closePopup() |
358 |
updateHeaderControls() |
|
359 |
changeUrl() |
|
387 |
if (!lockedDay && (currentTime === 23)) { |
|
388 |
addDayToCurrentDate(-1) |
|
389 |
await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute) |
|
390 |
} else { |
|
391 |
drawHeatmap(data[currentTime]) |
|
392 |
} |
|
360 | 393 |
} |
361 | 394 |
|
362 |
|
|
363 |
function next () { |
|
395 |
async function next () { |
|
396 |
if (loading) { |
|
397 |
return |
|
398 |
} |
|
364 | 399 |
currentTime = (currentTime + 1) % 24 |
365 |
drawHeatmap(data[currentTime]) |
|
366 |
setTimeline() |
|
400 |
changeHour(currentTime) |
|
367 | 401 |
mymap.closePopup() |
368 |
updateHeaderControls() |
|
369 |
changeUrl() |
|
402 |
if (!lockedDay && (currentTime === 0)) { |
|
403 |
addDayToCurrentDate(1) |
|
404 |
await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute) |
|
405 |
} else { |
|
406 |
drawHeatmap(data[currentTime]) |
|
407 |
} |
|
370 | 408 |
} |
371 | 409 |
|
372 |
|
|
373 | 410 |
/** |
374 | 411 |
* Change browser url based on animation step. |
375 | 412 |
*/ |
... | ... | |
377 | 414 |
window.history.pushState( |
378 | 415 |
'', |
379 | 416 |
document.title, |
380 |
window.location.origin + window.location.pathname + `?date=${$('#date').val()}&time=${currentTime}${datasetSelected.reduce((acc, current) => acc + '&type=' + current, '')}`
|
|
417 |
window.location.origin + window.location.pathname + `?date=${currentDateToString()}&time=${currentTime}${datasetSelected.reduce((acc, current) => acc + '&type=' + current, '')}`
|
|
381 | 418 |
) |
382 | 419 |
} |
383 | 420 |
|
384 |
|
|
385 | 421 |
function updateHeaderControls () { |
386 | 422 |
$(`#time_${currentTime}`).prop('checked', true) |
387 | 423 |
$('#dropdownMenuButtonTime').html((currentTime < 10 ? '0' : '') + `${currentTime}:00`) |
388 | 424 |
} |
389 | 425 |
|
390 |
|
|
391 | 426 |
function setTimeline () { |
392 | 427 |
$('#timeline').text(currentTime + ':00') |
393 | 428 |
$('#timeline').attr('class', 'time hour-' + currentTime) |
394 | 429 |
} |
395 |
|
|
396 |
|
|
397 |
function changeHour(hour) { |
|
398 |
currentTime = hour |
|
430 |
const onChangeHour = (hour) => { |
|
431 |
changeHour(hour) |
|
432 |
drawHeatmap(data[currentTime]) |
|
433 |
} |
|
434 |
const changeHour = (hour) => { |
|
435 |
changeCurrentTime(hour) |
|
399 | 436 |
updateHeaderControls() |
400 | 437 |
setTimeline() |
401 |
drawHeatmap(data[currentTime]) |
|
402 | 438 |
changeUrl() |
403 | 439 |
} |
404 | 440 |
|
405 |
|
|
406 | 441 |
/** |
407 | 442 |
* Load and display heatmap layer for current data |
408 | 443 |
* @param {string} opendataRoute route to dataset source |
... | ... | |
414 | 449 |
|
415 | 450 |
dataSourceRoute = opendataRoute |
416 | 451 |
positionsSourceRoute = positionsRoute |
417 |
const dataSourceMarks = {} |
|
418 | 452 |
const allPromises = [] |
419 |
const date = $('#date').val() |
|
420 |
currentTime = parseInt($('#dropdown-time input[type="radio"]:checked').val()) |
|
421 |
|
|
422 |
setTimeline() |
|
423 | 453 |
data[currentTime] = {} |
454 |
|
|
424 | 455 |
const dataSelectedHandler = async (datasetName) => { |
425 |
const marks = await fetchDataSourceMarks(positionsRoute, datasetName) |
|
426 |
const datasetData = await fetchByNameDate(dataSourceRoute, datasetName, date, currentTime) |
|
427 |
dataSourceMarks[datasetName] = marks |
|
456 |
if (!(datasetName in dataSourceMarks)) { |
|
457 |
dataSourceMarks[datasetName] = await fetchDataSourceMarks(positionsRoute, datasetName) |
|
458 |
} |
|
459 |
const datasetData = await fetchByNameDate(dataSourceRoute, datasetName, currentDateToString(), currentTime) |
|
428 | 460 |
data[currentTime][datasetName] = datasetData |
429 | 461 |
} |
430 |
|
|
431 |
await datasetSelected.forEach((datasetName) => { |
|
462 |
datasetSelected.forEach((datasetName) => { |
|
432 | 463 |
allPromises.push(dataSelectedHandler(datasetName)) |
433 | 464 |
}) |
434 | 465 |
|
466 |
loadingY() |
|
435 | 467 |
Promise.all(allPromises).then( |
436 | 468 |
() => { |
469 |
loadingN() |
|
437 | 470 |
drawDataSourceMarks(dataSourceMarks) |
438 | 471 |
drawHeatmap(data[currentTime]) |
439 |
preload(currentTime, 1, date)
|
|
440 |
preload(currentTime, -1, date)
|
|
472 |
preload(currentTime, 1, currentDateToString())
|
|
473 |
preload(currentTime, -1, currentDateToString())
|
|
441 | 474 |
} |
442 | 475 |
) |
443 | 476 |
} |
444 | 477 |
|
445 |
|
|
446 | 478 |
function drawDataSourceMarks (data) { |
447 | 479 |
if (marksLayer != null) { |
448 | 480 |
mymap.removeLayer(marksLayer) |
... | ... | |
469 | 501 |
marksLayer.setZIndex(-1).addTo(mymap) |
470 | 502 |
} |
471 | 503 |
|
472 |
|
|
473 | 504 |
async function preload (time, change, date) { |
505 |
loadingY() |
|
474 | 506 |
for (let nTime = time + change; nTime >= 0 && nTime <= 23; nTime = nTime + change) { |
475 | 507 |
if (!data[nTime]) { |
476 | 508 |
data[nTime] = {} |
... | ... | |
482 | 514 |
} |
483 | 515 |
}) |
484 | 516 |
} |
517 |
loadingN() |
|
485 | 518 |
} |
486 | 519 |
|
487 |
|
|
488 | 520 |
function drawHeatmap (dataRaw) { |
489 | 521 |
// Todo still switched |
490 | 522 |
const dataDict = dataRaw |
491 | 523 |
const mergedPoints = [] |
492 | 524 |
let max = 0 |
493 |
|
|
525 |
|
|
494 | 526 |
if (Object.keys(globalMarkersChanged).length) { |
495 | 527 |
Object.keys(globalMarkersChanged).forEach(function (key) { |
496 | 528 |
globalMarkersChanged[key][0].bindPopup(globalMarkersChanged[key][1]) |
... | ... | |
518 | 550 |
return [x, y, number] |
519 | 551 |
}) |
520 | 552 |
mergedPoints.push(...points) |
521 |
} |
|
522 |
else { |
|
553 |
} else { |
|
523 | 554 |
if (heatmapLayer != null) { |
524 | 555 |
mymap.removeLayer(heatmapLayer) |
525 | 556 |
} |
... | ... | |
535 | 566 |
} |
536 | 567 |
} |
537 | 568 |
|
538 |
|
|
539 | 569 |
/** |
540 | 570 |
* Checks dataset availibility |
541 | 571 |
* @param {string} route authority for datasets availibility checks |
... | ... | |
545 | 575 |
$.ajax({ |
546 | 576 |
type: 'POST', |
547 | 577 |
// Todo it might be good idea to change db collections format |
548 |
url: route + '/' + $('#date').val(),
|
|
578 |
url: route + '/' + currentDateToString(),
|
|
549 | 579 |
success: function (result) { |
550 | 580 |
updateAvailableDataSets(result) |
551 | 581 |
} |
552 | 582 |
}) |
553 | 583 |
} |
554 | 584 |
|
555 |
|
|
556 | 585 |
function updateAvailableDataSets (available) { |
557 | 586 |
let leastOneOptionEnabled = false |
558 |
// datasetSelected = [] |
|
559 | 587 |
|
560 | 588 |
$('#dropdown-dataset .dropdown-item').each(function () { |
561 | 589 |
const input = $(this).find('input') |
... | ... | |
564 | 592 |
if (!(inputVal in available)) { |
565 | 593 |
$(this).addClass('disabled') |
566 | 594 |
$(input).prop('checked', false) |
567 |
} |
|
568 |
else { |
|
595 |
} else { |
|
569 | 596 |
leastOneOptionEnabled = true |
570 | 597 |
$(this).removeClass('disabled') |
571 | 598 |
} |
... | ... | |
574 | 601 |
$('#btn-update-heatmap').prop('disabled', !leastOneOptionEnabled) |
575 | 602 |
} |
576 | 603 |
|
577 |
|
|
578 | 604 |
function formatDate (date) { |
579 | 605 |
var day = String(date.getDate()) |
580 | 606 |
var month = String(date.getMonth() + 1) |
... | ... | |
590 | 616 |
return date.getFullYear() + '-' + month + '-' + day |
591 | 617 |
} |
592 | 618 |
|
593 |
|
|
594 | 619 |
// eslint-disable-next-line no-unused-vars |
595 | 620 |
function initDatepicker (availableDatesSource) { |
596 | 621 |
var availableDates = '' |
... | ... | |
608 | 633 |
beforeShowDay: function (date) { |
609 | 634 |
if (availableDates.indexOf(formatDate(date)) < 0) { |
610 | 635 |
return { enabled: false, tooltip: 'Žádná data' } |
611 |
} |
|
612 |
else { |
|
636 |
} else { |
|
613 | 637 |
return { enabled: true } |
614 | 638 |
} |
615 | 639 |
}, |
... | ... | |
618 | 642 |
}) |
619 | 643 |
} |
620 | 644 |
|
621 |
|
|
622 | 645 |
function initLocationsMenu () { |
623 | 646 |
var locationsWrapper = '.locations' |
624 | 647 |
var locationsDisplayClass = 'show' |
625 | 648 |
|
626 | 649 |
if ($(window).width() <= 480) { |
627 | 650 |
$(locationsWrapper).removeClass(locationsDisplayClass) |
628 |
} |
|
629 |
else { |
|
651 |
} else { |
|
630 | 652 |
$(locationsWrapper).addClass(locationsDisplayClass) |
631 | 653 |
} |
632 | 654 |
} |
633 | 655 |
|
634 |
|
|
635 |
function openDatepicker () { |
|
636 |
if ($(window).width() <= 990) { |
|
637 |
$('.navbar-collapse').collapse() |
|
638 |
} |
|
639 |
|
|
640 |
$('#date').datepicker('show') |
|
641 |
} |
|
642 |
|
|
643 |
|
|
644 | 656 |
function onDocumentReady () { |
645 | 657 |
$('#dropdown-dataset').on('click', function (e) { |
646 | 658 |
e.stopPropagation() |
647 | 659 |
}) |
648 | 660 |
|
649 | 661 |
$('#btn-update-heatmap').prop('name', '') |
662 |
changeCurrentTime() |
|
663 |
setTimeline() |
|
664 |
changeCurrentDate() |
|
650 | 665 |
onValueChangeRegister() |
651 | 666 |
} |
652 | 667 |
|
653 |
|
|
654 | 668 |
const loadCheckboxDatasetNameData = () => { |
655 | 669 |
datasetSelected = [] |
656 | 670 |
$('#dropdown-dataset .dropdown-item').each(function () { |
Také k dispozici: Unified diff
done