Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 0bb62f35

Přidáno uživatelem kohlicekjan před více než 6 roky(ů)

Closes #11 migrace z Gulp.js na Webpack 4

Zobrazit rozdíly:

frontend/.eslintrc.json
1
{
2
  "extends": ["standard"]
3
}
frontend/.gitignore
1
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
2
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
3

  
4
# User-specific stuff
5
.idea/**/workspace.xml
6
.idea/**/tasks.xml
7
.idea/**/usage.statistics.xml
8
.idea/**/dictionaries
9
.idea/**/shelf
10

  
11
# Generated files
12
.idea/**/contentModel.xml
13

  
14
# Sensitive or high-churn files
15
.idea/**/dataSources/
16
.idea/**/dataSources.ids
17
.idea/**/dataSources.local.xml
18
.idea/**/sqlDataSources.xml
19
.idea/**/dynamic.xml
20
.idea/**/uiDesigner.xml
21
.idea/**/dbnavigator.xml
22

  
23
# Gradle
24
.idea/**/gradle.xml
25
.idea/**/libraries
26

  
27
# Gradle and Maven with auto-import
28
# When using Gradle or Maven with auto-import, you should exclude module files,
29
# since they will be recreated, and may cause churn.  Uncomment if using
30
# auto-import.
31
# .idea/modules.xml
32
# .idea/*.iml
33
# .idea/modules
34

  
35
# CMake
36
cmake-build-*/
37

  
38
# Mongo Explorer plugin
39
.idea/**/mongoSettings.xml
40

  
41
# File-based project format
42
*.iws
43

  
44
# IntelliJ
45
out/
46

  
47
# mpeltonen/sbt-idea plugin
48
.idea_modules/
49

  
50
# JIRA plugin
51
atlassian-ide-plugin.xml
52

  
53
# Cursive Clojure plugin
54
.idea/replstate.xml
55

  
56
# Crashlytics plugin (for Android Studio and IntelliJ)
57
com_crashlytics_export_strings.xml
58
crashlytics.properties
59
crashlytics-build.properties
60
fabric.properties
61

  
62
# Editor-based Rest Client
63
.idea/httpRequests
64

  
65

  
66
# Logs
67
logs
68
*.log
69
npm-debug.log*
70
yarn-debug.log*
71
yarn-error.log*
72

  
73
# Runtime data
74
pids
75
*.pid
76
*.seed
77
*.pid.lock
78

  
79
# Directory for instrumented libs generated by jscoverage/JSCover
80
lib-cov
81

  
82
# Coverage directory used by tools like istanbul
83
coverage
84

  
85
# nyc test coverage
86
.nyc_output
87

  
88
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
89
.grunt
90

  
91
# Bower dependency directory (https://bower.io/)
92
bower_components
93

  
94
# node-waf configuration
95
.lock-wscript
96

  
97
# Compiled binary addons (https://nodejs.org/api/addons.html)
98
build/Release
99

  
100
# Dependency directories
101
node_modules/
102
jspm_packages/
103

  
104
# TypeScript v1 declaration files
105
typings/
106

  
107
# Optional npm cache directory
108
.npm
109

  
110
# Optional eslint cache
111
.eslintcache
112

  
113
# Optional REPL history
114
.node_repl_history
115

  
116
# Output of 'npm pack'
117
*.tgz
118

  
119
# Yarn Integrity file
120
.yarn-integrity
121

  
122
# dotenv environment variables file
123
.env
124

  
125
# parcel-bundler cache (https://parceljs.org/)
126
.cache
127

  
128
# next.js build output
129
.next
130

  
131
# nuxt.js build output
132
.nuxt
133

  
134
# vuepress build output
135
.vuepress/dist
136

  
137
# Serverless directories
138
.serverless
139

  
140
# Build app
141
dist/
142

  
143
# Idea config project
144
.idea/
145

  
146
# Lighthouse log
147
latest-run/
148

  
149
# Flow-typed
150
flow-typed/
frontend/.stylelintrc
1 1
{
2
  "rules": {
3
  },
4 2
  "extends": [
5
    "stylelint-config-standard",
3
    "stylelint-config-recommended-scss",
6 4
    "stylelint-config-rational-order"
7 5
  ]
8
}
6
}
frontend/README.md
1
# Prujezd-vozidel-PK - frontend
2
Frontend pro zobrazení dat o průjezdu vozidel pro Plzeňský kraj
3

  
4
## Configuration
5

  
6
The configuration files are in the `./config/`.
7
The files `development.json` and `production.json` overwrite the main configuration file `default.json`.
8

  
9
This is the default configuration:
10
```json
11
{
12
  "API_URL": "http://students.kiv.zcu.cz/~valesz/index.php/api/v1",
13
  "TOKEN_GENERATOR_PATH": "../backend/lib/generateToken.php"
14
}
15
```
16

  
17
## Build
18

  
19
Install dependencies:
20

  
21
```bash
22
$ npm install
23
```
24

  
25
And build:
26

  
27
```bash
28
$ npm run build:production
29
```
30
Output directory is `./dist`
frontend/app.js
1
angular.module('pvpk', ['ngResource']);
2
angular.module('pvpk')
3
    .constant('config', {
4
        APP_NAME: 'PVPK',
5
        APP_VERSION: '1.3.3',
6
        API_URL: API_URL,
7
        API_TOKEN: API_TOKEN,
8
        DEFAULT_POSITION: {lat: 49.53, lng: 13.3},
9
        DEFAULT_ZOOM: 10,
10
        DEFAULT_ZOOM_MIN: 7,
11
        DEFAULT_RANGE_DATE_DAY: {from: -30, to: -1},
12
        DEFAULT_RANGE_TIME_HOUR: {from: 7, to: 16}
13
    });
14
angular.module('pvpk')
15
    .factory('Device', ['$resource', 'config', function ($resource, config) {
16
        return $resource(config.API_URL + '/devices/:id', {id: '@id', period: '@period'}, {
17
            'get': {
18
                url: config.API_URL + '/devices/:id/:period',
19
                method: 'GET',
20
                headers: {
21
                    'Content-Type': 'application/json',
22
                    'Accept': 'application/json',
23
                    'jwt': config.API_TOKEN
24
                }
25
            },
26
            'query': {
27
                url: config.API_URL + '/devices',
28
                method: 'GET',
29
                isArray: true,
30
                headers: {
31
                    'Content-Type': 'application/json',
32
                    'Accept': 'application/json',
33
                    'jwt': config.API_TOKEN
34
                }
35
            }
36
        });
37
    }]);
38
angular.module('pvpk')
39
    .factory('Range', ['$resource', 'config', function ($resource, config) {
40
        return $resource(config.API_URL + '/range', null, {
41
            'get': {
42
                url: config.API_URL + '/range',
43
                method: 'GET',
44
                headers: {
45
                    'Content-Type': 'application/json',
46
                    'Accept': 'application/json',
47
                    'jwt': config.API_TOKEN
48
                }
49
            }
50
        });
51
    }]);
52
angular.module('pvpk')
53
    .factory('Vehicle', ['$resource', 'config', function ($resource, config) {
54
        return $resource(config.API_URL + '/vehicles', null, {
55
            'query': {
56
                url: config.API_URL + '/vehicles',
57
                method: 'GET',
58
                isArray: true,
59
                headers: {
60
                    'Content-Type': 'application/json',
61
                    'Accept': 'application/json',
62
                    'jwt': config.API_TOKEN
63
                }
64
            }
65
        });
66
    }]);
67
angular.module('pvpk')
68
    .controller('infoController', ['$rootScope', '$scope', '$location', 'config', 'Device', 'Vehicle', 'Range', function ($rootScope, $scope, $location, config, Device, Vehicle, Range) {
69

  
70
        this.$onInit = function () {
71
            $rootScope.selectDevice = null;
72
            $scope.showInfoLoading = false;
73
            $scope.vehicles = [];
74
            $scope.urlExportCsv = null;
75
            $scope.directions = [
76
                {id: undefined, name: 'po směru i proti směru'},
77
                {id: 1, name: 'po směru'},
78
                {id: 2, name: 'proti směru'}];
79
            $scope.isLoadRange = false;
80

  
81
            Vehicle.query(null, function (data) {
82
                $scope.vehicles = data;
83
            }, function (response) {
84
                $rootScope.graphShow = false;
85
                console.log('Error api all Vehicles');
86
                $rootScope.handleErrorResponse(response);
87
            });
88

  
89
            $rootScope.$emit('setRangeFromUrl', null);
90
        };
91

  
92
        $rootScope.$on('setRangeFromUrl', function (event, args) {
93
            var params = $location.search();
94

  
95
            $scope.range = {
96
                fromDate: moment(params.fromDate, 'YYYY-MM-DD').isValid() ? moment(params.fromDate).toDate() : moment().add(config.DEFAULT_RANGE_DATE_DAY.from, 'd').toDate(),
97
                toDate: moment(params.toDate, 'YYYY-MM-DD').isValid() ? moment(params.toDate).toDate() : moment().add(config.DEFAULT_RANGE_DATE_DAY.to, 'd').toDate(),
98
                fromTime: moment(params.fromTime, 'HH:mm').isValid() ? moment(params.fromTime, 'HH:mm').toDate() : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.from}).toDate(),
99
                toTime: moment(params.toTime, 'HH:mm').isValid() ? moment(params.toTime, 'HH:mm').toDate() : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.to}).toDate(),
100
                isTime: params.isTime == 0 ? false : true,
101
                maxDate: $scope.range == null ? null : $scope.range.maxDate,
102
                minDate: $scope.range == null ? null : $scope.range.minDate
103
            };
104

  
105
            if (!$scope.isLoadRange) {
106
                Range.get(null, function (data) {
107
                    $scope.range.fromDate = moment.max(moment(data.last_date).add(config.DEFAULT_RANGE_DATE_DAY.from, 'd'), moment(data.first_date)).toDate();
108
                    $scope.range.toDate = moment.min(moment($scope.range.toDate), moment(data.last_date)).toDate();
109
                    $scope.range.maxDate = moment(data.last_date).toDate();
110
                    $scope.range.minDate = moment(data.first_date).toDate();
111
                    $scope.isLoadRange = true;
112
                }, function (response) {
113
                    console.log('Error api get Range');
114
                    $rootScope.handleErrorResponse(response);
115
                });
116
            }
117
        });
118

  
119
        $rootScope.$on('infoLocation', function (event, args) {
120
            $scope.showInfoLoading = true;
121

  
122
            var params = $location.search();
123
            params.deviceId = args.id;
124
            params.direction = args.direction;
125
            $location.search(params);
126

  
127
            var range = $scope.getRange();
128

  
129
            var query = {
130
                period: range.isTime ? 'time-period' : 'day-period',
131
                id: args.id,
132
                direction: args.direction,
133
                dateFrom: range.fromDate.format('YYYY-MM-DD'),
134
                dateTo: range.toDate.format('YYYY-MM-DD'),
135
                timeFrom: range.isTime ? range.fromTime.format('HH:mm') : null,
136
                timeTo: range.isTime ? range.toTime.format('HH:mm') : null
137
            };
138

  
139
            Device.get(query, function (data) {
140
                $rootScope.selectDevice = data;
141
                $scope.renderGraph();
142
                $scope.urlExportCsv = $scope.generateUrlExportCsv(query);
143

  
144
                $scope.showInfoLoading = false;
145
            }, function (response) {
146
                $rootScope.selectDevice = null;
147
                $scope.showInfoLoading = false;
148
                console.log('Error api get Devices');
149
                $rootScope.handleErrorResponse(response);
150
            });
151

  
152
        });
153

  
154
        $scope.generateUrlExportCsv = function (query) {
155
            var relativeUrl = '/devices/:id/:period/csv?'.replace(':id', query.id).replace(':period', query.period);
156
            delete query.id;
157
            delete query.period;
158

  
159
            var paramsUrl = jQuery.param(query);
160
            return config.API_URL + relativeUrl + paramsUrl;
161
        };
162

  
163
        $scope.changeRange = function () {
164
            if ($scope.range.fromDate > $scope.range.toDate || ($scope.range.isTime && $scope.range.fromTime >= $scope.range.toTime)) {
165
                $rootScope.selectDevice.traffics = [];
166
                return;
167
            }
168

  
169
            var range = $scope.getRange();
170

  
171
            var params = $location.search();
172
            params.fromDate = range.fromDate.format('YYYY-MM-DD');
173
            params.toDate = range.toDate.format('YYYY-MM-DD');
174
            params.fromTime = range.isTime ? range.fromTime.format('HH:mm') : null;
175
            params.toTime = range.isTime ? range.toTime.format('HH:mm') : null;
176
            params.isTime = range.isTime ? null : 0;
177
            $location.search(params);
178

  
179
            if ($rootScope.selectDevice)
180
                $rootScope.$emit('infoLocation', {
181
                    id: $rootScope.selectDevice.id,
182
                    direction: $rootScope.selectDevice.direction
183
                });
184
        };
185

  
186
        $scope.changeDirection = function () {
187

  
188
            $rootScope.$emit('infoLocation', {
189
                id: $rootScope.selectDevice.id,
190
                direction: $rootScope.selectDevice.direction
191
            });
192
        };
193

  
194
        $scope.getRange = function () {
195
            return {
196
                fromDate: moment($scope.range.fromDate).isValid() ? moment($scope.range.fromDate) : moment().add(config.DEFAULT_RANGE_DATE_DAY.from, 'd'),
197
                toDate: moment($scope.range.toDate).isValid() ? moment($scope.range.toDate) : moment().add(config.DEFAULT_RANGE_DATE_DAY.to, 'd'),
198
                fromTime: moment($scope.range.fromTime).isValid() ? moment($scope.range.fromTime) : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.from}),
199
                toTime: moment($scope.range.toTime).isValid() ? moment($scope.range.toTime) : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.to}),
200
                isTime: $scope.range.isTime ? true : false
201
            };
202
        };
203

  
204
        $scope.renderGraph = function () {
205
            var color = ['rgba(158, 158, 158, #alpha)', 'rgba(213, 0, 0, #alpha)', 'rgba(0, 123, 255, #alpha)', 'rgba(170, 0, 255, #alpha)',
206
                'rgba(0, 200, 83, #alpha)', 'rgba(255, 214, 0, #alpha)', 'rgba(255, 109, 0, #alpha)',
207
                'rgba(174, 234, 0, #alpha)', 'rgba(98, 0, 234, #alpha)', 'rgba(255, 171, 0, #alpha)', 'rgba(100, 221, 23, #alpha)', 'rgba(0, 184, 212, #alpha)'];
208

  
209
            var labels = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {
210
                return $scope.range.isTime ? d.timeFrom : moment(d.date, 'YYYY-MM-DD').format('D.M.YYYY');
211
            }));
212

  
213
            var useVehiclesIds = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {
214
                return d.typeVehicleId;
215
            }));
216

  
217
            var filterVehicles = jQuery.grep($scope.vehicles, function (n) {
218
                return useVehiclesIds.indexOf(n.id) >= 0;
219
            });
220

  
221
            var datasetsNumberVehicles = [];
222
            var datasetsAverageSpeed = [];
223

  
224
            for (var i = 0, vehicle; vehicle = filterVehicles[i]; i++) {
225
                var datasetNumberVehicles = {
226
                    label: vehicle.name,
227
                    backgroundColor: color[vehicle.id].replace("#alpha", "0.3"),
228
                    borderColor: color[vehicle.id].replace("#alpha", "1"),
229
                    borderWidth: 2,
230
                    data: []
231
                };
232

  
233
                var datasetAverageSpeed = {
234
                    data: [],
235
                    borderWidth: 2,
236
                    label: vehicle.name,
237
                    fill: false,
238
                    backgroundColor: color[vehicle.id].replace("#alpha", "0.3"),
239
                    borderColor: color[vehicle.id].replace("#alpha", "1"),
240
                    cubicInterpolationMode: 'monotone',
241
                    pointRadius: 0
242
                };
243

  
244
                var l = 0;
245
                for (var j = 0, traffic; traffic = $rootScope.selectDevice.traffics[j]; j++) {
246
                    if (($scope.range.isTime && labels[l] !== traffic.timeFrom) || (!$scope.range.isTime && labels[l] !== moment(traffic.date, 'YYYY-MM-DD').format('D.M.YYYY'))) {
247
                        l++;
248
                        if (datasetNumberVehicles.data.length < l) {
249
                            datasetNumberVehicles.data.push(0);
250
                            datasetAverageSpeed.data.push(0);
251
                        }
252
                    }
253
                    if (traffic.typeVehicleId === vehicle.id) {
254
                        datasetNumberVehicles.data.push($scope.range.isTime ? traffic.numberVehicleAverage : traffic.numberVehicle);
255
                        datasetAverageSpeed.data.push(traffic.speedAverage <= 0 ? 0 : traffic.speedAverage);
256
                    }
257
                }
258
                datasetsNumberVehicles.push(datasetNumberVehicles);
259
                datasetsAverageSpeed.push(datasetAverageSpeed);
260
            }
261

  
262
            $rootScope.$emit('renderGraphNumberVehicles', {
263
                data: {
264
                    labels: labels,
265
                    datasets: datasetsNumberVehicles
266
                }
267
            });
268

  
269
            $rootScope.$emit('renderGraphAverageSpeed', {
270
                data: {
271
                    labels: labels,
272
                    datasets: datasetsAverageSpeed
273
                }
274
            });
275
        };
276

  
277
        $scope.infoClose = function () {
278
            $rootScope.selectDevice = null;
279

  
280
            var params = $location.search();
281
            params.deviceId = null;
282
            params.direction = null;
283
            $location.search(params);
284

  
285
            $rootScope.$emit('setDefaultMap', null);
286
        };
287
    }]);
288

  
289
angular.module('pvpk')
290
    .controller('mainController', ['$rootScope', '$scope', '$location', '$window', function ($rootScope, $scope, $location, $window) {
291

  
292
        this.$onInit = function () {
293
            $scope.showLoadingScreen = true;
294
        };
295

  
296
        $window.onload = function () {
297
            var params = $location.search();
298
            if (params.deviceId) {
299
                $rootScope.$emit('activeMarker', {id: params.deviceId});
300
            }
301

  
302
            $scope.$apply(function () {
303
                $scope.showLoadingScreen = false;
304
            });
305
        };
306

  
307
        $rootScope.$on('$locationChangeSuccess', function (event, newUrl, oldUrl) {
308
            var params = $location.search();
309

  
310
            if (newUrl !== oldUrl && $scope.historyUrl) {
311
                if ($scope.historyUrl.q !== $scope.historyUrl.q || $scope.historyUrl.isDirection != params.isDirection) {
312
                    $rootScope.$emit('setSearchFromUrl', null);
313
                }
314

  
315
                if ($scope.historyUrl.fromDate !== params.fromDate || $scope.historyUrl.toDate !== params.toDate ||
316
                    $scope.historyUrl.fromTime !== params.fromTime || $scope.historyUrl.toTime !== params.toTime) {
317
                    $rootScope.$emit('setRangeFromUrl', null);
318
                    if (params.deviceId) {
319
                        $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
320
                    }
321
                } else if (params.deviceId && ($scope.historyUrl.deviceId !== params.deviceId || $scope.historyUrl.direction !== params.direction)) {
322
                    $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
323
                    $rootScope.$emit('activeMarker', {id: params.deviceId});
324
                } else if (!params.deviceId && $scope.historyUrl.deviceId) {
325
                    $rootScope.selectDevice = null;
326
                    $rootScope.$emit('setDefaultMap', null);
327
                }
328
            } else if (params.deviceId) {
329
                $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
330
            }
331

  
332
            $scope.historyUrl = $location.search();
333
        });
334

  
335
        $rootScope.handleErrorResponse = function (response) {
336

  
337
            var modalError = jQuery('#modalError');
338
            switch (response.status) {
339
                case 400:
340
                    console.log('API ERROR 400');
341
                    $scope.modalError = {
342
                        title: 'Neplatný požadavek',
343
                        body: 'Požadavek nemůže být vyřízen, poněvadž byl syntakticky nesprávně zapsán.',
344
                        button: 'OK'
345
                    };
346
                    modalError.modal('show');
347
                    break;
348
                case 401:
349
                    $scope.modalError = {
350
                        title: 'Platnost webové aplikace vypršela',
351
                        body: 'Pro obnovení platnosti stačí stisknout tlačítko Obnovit.',
352
                        button: 'Obnovit',
353
                        clickButton: $scope.reloadApp
354
                    };
355
                    modalError.modal({backdrop: 'static', keyboard: false});
356
                    break;
357
                case 404:
358
                    console.log('API ERROR 404');
359
                    $scope.modalError = {title: 'Nenalezen', body: 'Záznam nebyl nalezen.', button: 'OK'};
360
                    modalError.modal('show');
361
                    break;
362
                case 500:
363
                    console.log('API ERROR 500');
364
                    $scope.modalError = {title: 'Chyba', body: 'Chyba serveru. Zopakujte akci později.', button: 'OK'};
365
                    modalError.modal('show');
366
                    break;
367
                case -1:
368
                    console.log('API NOT CONNECTED');
369
                    $scope.modalError = {
370
                        title: 'Připojení k internetu',
371
                        body: 'Nejste připojeni k internetu. Zkontrolujte připojení.',
372
                        button: 'OK'
373
                    };
374
                    modalError.modal('show');
375
                    break;
376
                default:
377
                    console.log('API UNKNOWN ERROR');
378
                    $scope.modalError = {title: 'Neočekávaná chyba', body: 'Nastala neočekávaná chyba.', button: 'OK'};
379
                    modalError.modal('show');
380
                    break;
381
            }
382
        };
383

  
384
        $scope.reloadApp = function () {
385
            $window.location.reload();
386
        }
387
    }]);
388
angular.module('pvpk')
389
    .controller('mapController', ['$rootScope', '$scope', 'config', 'Device', function ($rootScope, $scope, config, Device) {
390

  
391
        this.$onInit = function () {
392
            $scope.markers = [];
393

  
394
            $scope.map = new google.maps.Map(document.getElementById('map'), {
395
                center: config.DEFAULT_POSITION,
396
                zoom: config.DEFAULT_ZOOM,
397
                minZoom: config.DEFAULT_ZOOM_MIN,
398
                zoomControl: true,
399
                mapTypeControl: false,
400
                scaleControl: false,
401
                streetViewControl: false,
402
                rotateControl: false,
403
                fullscreenControl: false,
404
                mapTypeId: google.maps.MapTypeId.ROADMAP
405
            });
406

  
407
            Device.query({showDirection: 0}, function (data) {
408
                for (var i = 0, lctn; lctn = data[i]; i++) {
409
                    $scope.createMarker(lctn);
410
                }
411
            }, function (response) {
412
                console.log('Error api all Devices');
413
                $rootScope.handleErrorResponse(response);
414
            });
415
        };
416

  
417
        $scope.createMarker = function (lctn) {
418
            if (lctn.lat && lctn.lng) {
419
                var marker = new google.maps.Marker({
420
                    map: $scope.map,
421
                    position: {lat: lctn.lat, lng: lctn.lng},
422
                    title: lctn.name,
423
                    infoWindow: new google.maps.InfoWindow({
424
                        content: '<h6 class="mb-1">' + lctn.name + '</h6>'
425
                        + '<address>' + lctn.street + ', ' + lctn.town + '</address>'
426
                    }),
427
                    id: lctn.id
428
                });
429

  
430
                marker.addListener('click', function () {
431
                    $scope.closeInfoWindows();
432
                    marker.infoWindow.open($scope.map, marker);
433
                    $rootScope.$emit('infoLocation', {id: lctn.id});
434
                });
435

  
436
                $scope.markers.push(marker);
437
            }
438
        };
439

  
440
        $rootScope.$on('activeMarker', function (event, args) {
441
            for (var i = 0, marker; marker = $scope.markers[i]; i++) {
442
                if (marker.id && marker.id === args.id && marker.infoWindow) {
443
                    $scope.map.setCenter(marker.getPosition());
444
                    $scope.map.setZoom(12);
445
                    marker.infoWindow.open($scope.map, marker);
446
                } else {
447
                    marker.infoWindow.close();
448
                }
449
            }
450
        });
451

  
452
        $rootScope.$on('setDefaultMap', function (event, args) {
453
            $scope.map.setCenter(config.DEFAULT_POSITION);
454
            $scope.map.setZoom(config.DEFAULT_ZOOM);
455
            $scope.closeInfoWindows();
456
        });
457

  
458
        $scope.closeInfoWindows = function () {
459
            for (var i = 0, marker; marker = $scope.markers[i]; i++) {
460
                marker.infoWindow.close();
461
            }
462
        };
463
    }]);
464
angular.module('pvpk')
465
    .controller('searchController', ['$rootScope', '$scope', '$location', 'config', 'Device', function ($rootScope, $scope, $location, config, Device) {
466

  
467
        this.$onInit = function () {
468
            $scope.config = config;
469
            $scope.locations = [];
470
            $scope.showSearchLoading = false;
471

  
472
            $rootScope.$emit('setSearchFromUrl', null);
473
        };
474

  
475
        $scope.searchLocations = function () {
476
            var params = $location.search();
477
            params.q = $scope.search.q;
478
            params.isDirection = $scope.search.isDirection ? 1 : null;
479
            $location.search(params);
480

  
481
            if (!$scope.search.q || $scope.search.q.length <= 1) {
482
                $scope.locations = [];
483
                return;
484
            }
485

  
486
            $scope.showSearchLoading = true;
487

  
488
            Device.query({
489
                address: $scope.search.q,
490
                showDirection: $scope.search.isDirection ? 1 : 0
491
            }, function (data) {
492
                $scope.locations = data;
493
                $scope.showSearchLoading = false;
494
            }, function (response) {
495
                $scope.showSearchLoading = false;
496
                console.log('Error api all Devices');
497
                $rootScope.handleErrorResponse(response);
498
            });
499
        };
500

  
501
        $rootScope.$on('setSearchFromUrl', function (event, args) {
502
            var params = $location.search();
503
            $scope.search = {
504
                q: params.q,
505
                isDirection: params.isDirection ? !!+params.isDirection : false
506
            };
507
            $scope.searchLocations();
508
        });
509

  
510
        $scope.selectDevice = function (id, direction) {
511
            $rootScope.$emit('activeMarker', {id: id});
512
            $rootScope.$emit('infoLocation', {id: id, direction: direction});
513
        };
514

  
515
    }]);
516
angular.module('pvpk')
517
    .component('graphAverageSpeed', {
518
        template: '<div><canvas id="graphAverageSpeed" class="graph-size mb-5"></canvas></div>',
519
        controller: ['$rootScope', '$scope', function ($rootScope, $scope) {
520

  
521
            $rootScope.$on('renderGraphAverageSpeed', function (event, args) {
522
                var canvas = document.getElementById('graphAverageSpeed').getContext('2d');
523

  
524
                if ($scope.graphLine)
525
                    $scope.graphLine.destroy();
526

  
527
                $scope.graphLine = new Chart(canvas, {
528
                    type: 'line',
529
                    data: args.data,
530
                    options: {
531
                        responsive: true,
532
                        pointDot: false,
533
                        legend: {
534
                            position: 'bottom'
535
                        },
536
                        scales: {
537
                            xAxes: [{
538
                                ticks: {
539
                                    autoSkip: true,
540
                                    maxTicksLimit: 15
541
                                }
542
                            }],
543
                            yAxes: [{
544
                                scaleLabel: {
545
                                    display: true,
546
                                    labelString: 'km/h'
547
                                },
548
                                ticks: {
549
                                    beginAtZero: true,
550
                                    suggestedMax: 70
551
                                }
552
                            }]
553
                        },
554
                        tooltips: {
555
                            mode: 'index',
556
                            intersect: false,
557
                            callbacks: {
558
                                label: function (tooltipItems) {
559
                                    return tooltipItems.yLabel + ' km/h';
560
                                }
561
                            }
562
                        }
563
                    }
564
                });
565

  
566
            });
567

  
568
        }]
569
    });
570
angular.module('pvpk')
571
    .component('graphNumberVehicles', {
572
        template: '<div><canvas id="graphNumberVehicles" class="graph-size mb-5"></canvas></div>',
573
        controller: ['$rootScope', '$scope', function ($rootScope, $scope) {
574

  
575
            $rootScope.$on('renderGraphNumberVehicles', function (event, args) {
576
                var canvasGraphNumberVehicles = document.getElementById('graphNumberVehicles').getContext('2d');
577

  
578
                if ($scope.graphNumberVehicles)
579
                    $scope.graphNumberVehicles.destroy();
580

  
581
                $scope.graphNumberVehicles = new Chart(canvasGraphNumberVehicles, {
582
                    type: 'bar',
583
                    data: args.data,
584
                    options: {
585
                        responsive: true,
586
                        onResize: function (chart, size) {
587
                            chart.options.legend.display = size.height > 240;
588
                            chart.update();
589
                        },
590
                        legend: {
591
                            position: 'bottom'
592
                        },
593
                        scales: {
594
                            xAxes: [{
595
                                stacked: true,
596
                                ticks: {
597
                                    autoSkip: true,
598
                                    maxTicksLimit: 15
599
                                }
600
                            }],
601
                            yAxes: [{
602
                                scaleLabel: {
603
                                    display: true,
604
                                    labelString: "počet vozidel"
605
                                },
606
                                stacked: true
607
                            }]
608
                        },
609
                        tooltips: {
610
                            mode: 'index',
611
                            intersect: false
612
                        }
613
                    }
614
                });
615

  
616
            });
617

  
618
        }]
619
    });
frontend/app.min.js
1
angular.module("pvpk",["ngResource"]),angular.module("pvpk").constant("config",{APP_NAME:"PVPK",APP_VERSION:"1.3.3",API_URL:API_URL,API_TOKEN:API_TOKEN,DEFAULT_POSITION:{lat:49.53,lng:13.3},DEFAULT_ZOOM:10,DEFAULT_ZOOM_MIN:7,DEFAULT_RANGE_DATE_DAY:{from:-30,to:-1},DEFAULT_RANGE_TIME_HOUR:{from:7,to:16}}),angular.module("pvpk").factory("Device",["$resource","config",function(e,o){return e(o.API_URL+"/devices/:id",{id:"@id",period:"@period"},{get:{url:o.API_URL+"/devices/:id/:period",method:"GET",headers:{"Content-Type":"application/json",Accept:"application/json",jwt:o.API_TOKEN}},query:{url:o.API_URL+"/devices",method:"GET",isArray:!0,headers:{"Content-Type":"application/json",Accept:"application/json",jwt:o.API_TOKEN}}})}]),angular.module("pvpk").factory("Range",["$resource","config",function(e,o){return e(o.API_URL+"/range",null,{get:{url:o.API_URL+"/range",method:"GET",headers:{"Content-Type":"application/json",Accept:"application/json",jwt:o.API_TOKEN}}})}]),angular.module("pvpk").factory("Vehicle",["$resource","config",function(e,o){return e(o.API_URL+"/vehicles",null,{query:{url:o.API_URL+"/vehicles",method:"GET",isArray:!0,headers:{"Content-Type":"application/json",Accept:"application/json",jwt:o.API_TOKEN}}})}]),angular.module("pvpk").controller("infoController",["$rootScope","$scope","$location","config","Device","Vehicle","Range",function(u,h,i,a,r,e,n){this.$onInit=function(){u.selectDevice=null,h.showInfoLoading=!1,h.vehicles=[],h.urlExportCsv=null,h.directions=[{id:void 0,name:"po směru i proti směru"},{id:1,name:"po směru"},{id:2,name:"proti směru"}],h.isLoadRange=!1,e.query(null,function(e){h.vehicles=e},function(e){u.graphShow=!1,console.log("Error api all Vehicles"),u.handleErrorResponse(e)}),u.$emit("setRangeFromUrl",null)},u.$on("setRangeFromUrl",function(e,o){var t=i.search();h.range={fromDate:moment(t.fromDate,"YYYY-MM-DD").isValid()?moment(t.fromDate).toDate():moment().add(a.DEFAULT_RANGE_DATE_DAY.from,"d").toDate(),toDate:moment(t.toDate,"YYYY-MM-DD").isValid()?moment(t.toDate).toDate():moment().add(a.DEFAULT_RANGE_DATE_DAY.to,"d").toDate(),fromTime:moment(t.fromTime,"HH:mm").isValid()?moment(t.fromTime,"HH:mm").toDate():moment({hour:a.DEFAULT_RANGE_TIME_HOUR.from}).toDate(),toTime:moment(t.toTime,"HH:mm").isValid()?moment(t.toTime,"HH:mm").toDate():moment({hour:a.DEFAULT_RANGE_TIME_HOUR.to}).toDate(),isTime:0!=t.isTime,maxDate:null==h.range?null:h.range.maxDate,minDate:null==h.range?null:h.range.minDate},h.isLoadRange||n.get(null,function(e){h.range.fromDate=moment.max(moment(e.last_date).add(a.DEFAULT_RANGE_DATE_DAY.from,"d"),moment(e.first_date)).toDate(),h.range.toDate=moment.min(moment(h.range.toDate),moment(e.last_date)).toDate(),h.range.maxDate=moment(e.last_date).toDate(),h.range.minDate=moment(e.first_date).toDate(),h.isLoadRange=!0},function(e){console.log("Error api get Range"),u.handleErrorResponse(e)})}),u.$on("infoLocation",function(e,o){h.showInfoLoading=!0;var t=i.search();t.deviceId=o.id,t.direction=o.direction,i.search(t);var a=h.getRange(),n={period:a.isTime?"time-period":"day-period",id:o.id,direction:o.direction,dateFrom:a.fromDate.format("YYYY-MM-DD"),dateTo:a.toDate.format("YYYY-MM-DD"),timeFrom:a.isTime?a.fromTime.format("HH:mm"):null,timeTo:a.isTime?a.toTime.format("HH:mm"):null};r.get(n,function(e){u.selectDevice=e,h.renderGraph(),h.urlExportCsv=h.generateUrlExportCsv(n),h.showInfoLoading=!1},function(e){u.selectDevice=null,h.showInfoLoading=!1,console.log("Error api get Devices"),u.handleErrorResponse(e)})}),h.generateUrlExportCsv=function(e){var o="/devices/:id/:period/csv?".replace(":id",e.id).replace(":period",e.period);delete e.id,delete e.period;var t=jQuery.param(e);return a.API_URL+o+t},h.changeRange=function(){if(h.range.fromDate>h.range.toDate||h.range.isTime&&h.range.fromTime>=h.range.toTime)u.selectDevice.traffics=[];else{var e=h.getRange(),o=i.search();o.fromDate=e.fromDate.format("YYYY-MM-DD"),o.toDate=e.toDate.format("YYYY-MM-DD"),o.fromTime=e.isTime?e.fromTime.format("HH:mm"):null,o.toTime=e.isTime?e.toTime.format("HH:mm"):null,o.isTime=e.isTime?null:0,i.search(o),u.selectDevice&&u.$emit("infoLocation",{id:u.selectDevice.id,direction:u.selectDevice.direction})}},h.changeDirection=function(){u.$emit("infoLocation",{id:u.selectDevice.id,direction:u.selectDevice.direction})},h.getRange=function(){return{fromDate:moment(h.range.fromDate).isValid()?moment(h.range.fromDate):moment().add(a.DEFAULT_RANGE_DATE_DAY.from,"d"),toDate:moment(h.range.toDate).isValid()?moment(h.range.toDate):moment().add(a.DEFAULT_RANGE_DATE_DAY.to,"d"),fromTime:moment(h.range.fromTime).isValid()?moment(h.range.fromTime):moment({hour:a.DEFAULT_RANGE_TIME_HOUR.from}),toTime:moment(h.range.toTime).isValid()?moment(h.range.toTime):moment({hour:a.DEFAULT_RANGE_TIME_HOUR.to}),isTime:!!h.range.isTime}},h.renderGraph=function(){for(var e,o=["rgba(158, 158, 158, #alpha)","rgba(213, 0, 0, #alpha)","rgba(0, 123, 255, #alpha)","rgba(170, 0, 255, #alpha)","rgba(0, 200, 83, #alpha)","rgba(255, 214, 0, #alpha)","rgba(255, 109, 0, #alpha)","rgba(174, 234, 0, #alpha)","rgba(98, 0, 234, #alpha)","rgba(255, 171, 0, #alpha)","rgba(100, 221, 23, #alpha)","rgba(0, 184, 212, #alpha)"],t=jQuery.unique(u.selectDevice.traffics.map(function(e){return h.range.isTime?e.timeFrom:moment(e.date,"YYYY-MM-DD").format("D.M.YYYY")})),a=jQuery.unique(u.selectDevice.traffics.map(function(e){return e.typeVehicleId})),n=jQuery.grep(h.vehicles,function(e){return 0<=a.indexOf(e.id)}),i=[],r=[],l=0;e=n[l];l++){for(var c,s={label:e.name,backgroundColor:o[e.id].replace("#alpha","0.3"),borderColor:o[e.id].replace("#alpha","1"),borderWidth:2,data:[]},m={data:[],borderWidth:2,label:e.name,fill:!1,backgroundColor:o[e.id].replace("#alpha","0.3"),borderColor:o[e.id].replace("#alpha","1"),cubicInterpolationMode:"monotone",pointRadius:0},d=0,p=0;c=u.selectDevice.traffics[p];p++)(h.range.isTime&&t[d]!==c.timeFrom||!h.range.isTime&&t[d]!==moment(c.date,"YYYY-MM-DD").format("D.M.YYYY"))&&(d++,s.data.length<d&&(s.data.push(0),m.data.push(0))),c.typeVehicleId===e.id&&(s.data.push(h.range.isTime?c.numberVehicleAverage:c.numberVehicle),m.data.push(c.speedAverage<=0?0:c.speedAverage));i.push(s),r.push(m)}u.$emit("renderGraphNumberVehicles",{data:{labels:t,datasets:i}}),u.$emit("renderGraphAverageSpeed",{data:{labels:t,datasets:r}})},h.infoClose=function(){u.selectDevice=null;var e=i.search();e.deviceId=null,e.direction=null,i.search(e),u.$emit("setDefaultMap",null)}}]),angular.module("pvpk").controller("mainController",["$rootScope","$scope","$location","$window",function(n,i,r,e){this.$onInit=function(){i.showLoadingScreen=!0},e.onload=function(){var e=r.search();e.deviceId&&n.$emit("activeMarker",{id:e.deviceId}),i.$apply(function(){i.showLoadingScreen=!1})},n.$on("$locationChangeSuccess",function(e,o,t){var a=r.search();o!==t&&i.historyUrl?(i.historyUrl.q==i.historyUrl.q&&i.historyUrl.isDirection==a.isDirection||n.$emit("setSearchFromUrl",null),i.historyUrl.fromDate!==a.fromDate||i.historyUrl.toDate!==a.toDate||i.historyUrl.fromTime!==a.fromTime||i.historyUrl.toTime!==a.toTime?(n.$emit("setRangeFromUrl",null),a.deviceId&&n.$emit("infoLocation",{id:a.deviceId,direction:a.direction})):!a.deviceId||i.historyUrl.deviceId===a.deviceId&&i.historyUrl.direction===a.direction?!a.deviceId&&i.historyUrl.deviceId&&(n.selectDevice=null,n.$emit("setDefaultMap",null)):(n.$emit("infoLocation",{id:a.deviceId,direction:a.direction}),n.$emit("activeMarker",{id:a.deviceId}))):a.deviceId&&n.$emit("infoLocation",{id:a.deviceId,direction:a.direction}),i.historyUrl=r.search()}),n.handleErrorResponse=function(e){var o=jQuery("#modalError");switch(e.status){case 400:console.log("API ERROR 400"),i.modalError={title:"Neplatný požadavek",body:"Požadavek nemůže být vyřízen, poněvadž byl syntakticky nesprávně zapsán.",button:"OK"},o.modal("show");break;case 401:i.modalError={title:"Platnost webové aplikace vypršela",body:"Pro obnovení platnosti stačí stisknout tlačítko Obnovit.",button:"Obnovit",clickButton:i.reloadApp},o.modal({backdrop:"static",keyboard:!1});break;case 404:console.log("API ERROR 404"),i.modalError={title:"Nenalezen",body:"Záznam nebyl nalezen.",button:"OK"},o.modal("show");break;case 500:console.log("API ERROR 500"),i.modalError={title:"Chyba",body:"Chyba serveru. Zopakujte akci později.",button:"OK"},o.modal("show");break;case-1:console.log("API NOT CONNECTED"),i.modalError={title:"Připojení k internetu",body:"Nejste připojeni k internetu. Zkontrolujte připojení.",button:"OK"},o.modal("show");break;default:console.log("API UNKNOWN ERROR"),i.modalError={title:"Neočekávaná chyba",body:"Nastala neočekávaná chyba.",button:"OK"},o.modal("show")}},i.reloadApp=function(){e.location.reload()}}]),angular.module("pvpk").controller("mapController",["$rootScope","$scope","config","Device",function(t,n,a,e){this.$onInit=function(){n.markers=[],n.map=new google.maps.Map(document.getElementById("map"),{center:a.DEFAULT_POSITION,zoom:a.DEFAULT_ZOOM,minZoom:a.DEFAULT_ZOOM_MIN,zoomControl:!0,mapTypeControl:!1,scaleControl:!1,streetViewControl:!1,rotateControl:!1,fullscreenControl:!1,mapTypeId:google.maps.MapTypeId.ROADMAP}),e.query({showDirection:0},function(e){for(var o,t=0;o=e[t];t++)n.createMarker(o)},function(e){console.log("Error api all Devices"),t.handleErrorResponse(e)})},n.createMarker=function(e){if(e.lat&&e.lng){var o=new google.maps.Marker({map:n.map,position:{lat:e.lat,lng:e.lng},title:e.name,infoWindow:new google.maps.InfoWindow({content:'<h6 class="mb-1">'+e.name+"</h6><address>"+e.street+", "+e.town+"</address>"}),id:e.id});o.addListener("click",function(){n.closeInfoWindows(),o.infoWindow.open(n.map,o),t.$emit("infoLocation",{id:e.id})}),n.markers.push(o)}},t.$on("activeMarker",function(e,o){for(var t,a=0;t=n.markers[a];a++)t.id&&t.id===o.id&&t.infoWindow?(n.map.setCenter(t.getPosition()),n.map.setZoom(12),t.infoWindow.open(n.map,t)):t.infoWindow.close()}),t.$on("setDefaultMap",function(e,o){n.map.setCenter(a.DEFAULT_POSITION),n.map.setZoom(a.DEFAULT_ZOOM),n.closeInfoWindows()}),n.closeInfoWindows=function(){for(var e,o=0;e=n.markers[o];o++)e.infoWindow.close()}}]),angular.module("pvpk").controller("searchController",["$rootScope","$scope","$location","config","Device",function(t,a,n,e,o){this.$onInit=function(){a.config=e,a.locations=[],a.showSearchLoading=!1,t.$emit("setSearchFromUrl",null)},a.searchLocations=function(){var e=n.search();e.q=a.search.q,e.isDirection=a.search.isDirection?1:null,n.search(e),!a.search.q||a.search.q.length<=1?a.locations=[]:(a.showSearchLoading=!0,o.query({address:a.search.q,showDirection:a.search.isDirection?1:0},function(e){a.locations=e,a.showSearchLoading=!1},function(e){a.showSearchLoading=!1,console.log("Error api all Devices"),t.handleErrorResponse(e)}))},t.$on("setSearchFromUrl",function(e,o){var t=n.search();a.search={q:t.q,isDirection:!!t.isDirection&&!!+t.isDirection},a.searchLocations()}),a.selectDevice=function(e,o){t.$emit("activeMarker",{id:e}),t.$emit("infoLocation",{id:e,direction:o})}}]),angular.module("pvpk").component("graphAverageSpeed",{template:'<div><canvas id="graphAverageSpeed" class="graph-size mb-5"></canvas></div>',controller:["$rootScope","$scope",function(e,a){e.$on("renderGraphAverageSpeed",function(e,o){var t=document.getElementById("graphAverageSpeed").getContext("2d");a.graphLine&&a.graphLine.destroy(),a.graphLine=new Chart(t,{type:"line",data:o.data,options:{responsive:!0,pointDot:!1,legend:{position:"bottom"},scales:{xAxes:[{ticks:{autoSkip:!0,maxTicksLimit:15}}],yAxes:[{scaleLabel:{display:!0,labelString:"km/h"},ticks:{beginAtZero:!0,suggestedMax:70}}]},tooltips:{mode:"index",intersect:!1,callbacks:{label:function(e){return e.yLabel+" km/h"}}}}})})}]}),angular.module("pvpk").component("graphNumberVehicles",{template:'<div><canvas id="graphNumberVehicles" class="graph-size mb-5"></canvas></div>',controller:["$rootScope","$scope",function(e,a){e.$on("renderGraphNumberVehicles",function(e,o){var t=document.getElementById("graphNumberVehicles").getContext("2d");a.graphNumberVehicles&&a.graphNumberVehicles.destroy(),a.graphNumberVehicles=new Chart(t,{type:"bar",data:o.data,options:{responsive:!0,onResize:function(e,o){e.options.legend.display=240<o.height,e.update()},legend:{position:"bottom"},scales:{xAxes:[{stacked:!0,ticks:{autoSkip:!0,maxTicksLimit:15}}],yAxes:[{scaleLabel:{display:!0,labelString:"počet vozidel"},stacked:!0}]},tooltips:{mode:"index",intersect:!1}}})})}]});
2
//# sourceMappingURL=app.min.js.map
frontend/app.min.js.map
1
{"version":3,"sources":["app.module.js","app.config.js","DeviceService.js","RangeService.js","VehicleService.js","infoController.js","mainController.js","mapController.js","searchController.js","graphAverageSpeed.js","graphNumberVehicles.js"],"names":["angular","module","constant","APP_NAME","APP_VERSION","API_URL","API_TOKEN","DEFAULT_POSITION","lat","lng","DEFAULT_ZOOM","DEFAULT_ZOOM_MIN","DEFAULT_RANGE_DATE_DAY","from","to","DEFAULT_RANGE_TIME_HOUR","factory","$resource","config","id","period","get","url","method","headers","Content-Type","Accept","jwt","query","isArray","controller","$rootScope","$scope","$location","Device","Vehicle","Range","this","$onInit","selectDevice","showInfoLoading","vehicles","urlExportCsv","directions","undefined","name","isLoadRange","data","response","graphShow","console","log","handleErrorResponse","$emit","$on","event","args","params","search","range","fromDate","moment","isValid","toDate","add","fromTime","hour","toTime","isTime","maxDate","minDate","max","last_date","first_date","min","deviceId","direction","getRange","dateFrom","format","dateTo","timeFrom","timeTo","renderGraph","generateUrlExportCsv","relativeUrl","replace","paramsUrl","jQuery","param","changeRange","traffics","changeDirection","vehicle","color","labels","unique","map","d","date","useVehiclesIds","typeVehicleId","filterVehicles","grep","n","indexOf","datasetsNumberVehicles","datasetsAverageSpeed","i","traffic","datasetNumberVehicles","label","backgroundColor","borderColor","borderWidth","datasetAverageSpeed","fill","cubicInterpolationMode","pointRadius","l","j","length","push","numberVehicleAverage","numberVehicle","speedAverage","datasets","infoClose","$window","showLoadingScreen","onload","$apply","newUrl","oldUrl","historyUrl","q","isDirection","modalError","status","title","body","button","modal","clickButton","reloadApp","backdrop","keyboard","location","reload","markers","google","maps","Map","document","getElementById","center","zoom","minZoom","zoomControl","mapTypeControl","scaleControl","streetViewControl","rotateControl","fullscreenControl","mapTypeId","MapTypeId","ROADMAP","showDirection","lctn","createMarker","marker","Marker","position","infoWindow","InfoWindow","content","street","town","addListener","closeInfoWindows","open","setCenter","getPosition","setZoom","close","locations","showSearchLoading","searchLocations","address","component","template","canvas","getContext","graphLine","destroy","Chart","type","options","responsive","pointDot","legend","scales","xAxes","ticks","autoSkip","maxTicksLimit","yAxes","scaleLabel","display","labelString","beginAtZero","suggestedMax","tooltips","mode","intersect","callbacks","tooltipItems","yLabel","canvasGraphNumberVehicles","graphNumberVehicles","onResize","chart","size","height","update","stacked"],"mappings":"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","file":"app.min.js","sourcesContent":["angular.module('pvpk', ['ngResource']);","angular.module('pvpk')\r\n    .constant('config', {\r\n        APP_NAME: 'PVPK',\r\n        APP_VERSION: '1.3.3',\r\n        API_URL: API_URL,\r\n        API_TOKEN: API_TOKEN,\r\n        DEFAULT_POSITION: {lat: 49.53, lng: 13.3},\r\n        DEFAULT_ZOOM: 10,\r\n        DEFAULT_ZOOM_MIN: 7,\r\n        DEFAULT_RANGE_DATE_DAY: {from: -30, to: -1},\r\n        DEFAULT_RANGE_TIME_HOUR: {from: 7, to: 16}\r\n    });","angular.module('pvpk')\r\n    .factory('Device', ['$resource', 'config', function ($resource, config) {\r\n        return $resource(config.API_URL + '/devices/:id', {id: '@id', period: '@period'}, {\r\n            'get': {\r\n                url: config.API_URL + '/devices/:id/:period',\r\n                method: 'GET',\r\n                headers: {\r\n                    'Content-Type': 'application/json',\r\n                    'Accept': 'application/json',\r\n                    'jwt': config.API_TOKEN\r\n                }\r\n            },\r\n            'query': {\r\n                url: config.API_URL + '/devices',\r\n                method: 'GET',\r\n                isArray: true,\r\n                headers: {\r\n                    'Content-Type': 'application/json',\r\n                    'Accept': 'application/json',\r\n                    'jwt': config.API_TOKEN\r\n                }\r\n            }\r\n        });\r\n    }]);","angular.module('pvpk')\r\n    .factory('Range', ['$resource', 'config', function ($resource, config) {\r\n        return $resource(config.API_URL + '/range', null, {\r\n            'get': {\r\n                url: config.API_URL + '/range',\r\n                method: 'GET',\r\n                headers: {\r\n                    'Content-Type': 'application/json',\r\n                    'Accept': 'application/json',\r\n                    'jwt': config.API_TOKEN\r\n                }\r\n            }\r\n        });\r\n    }]);","angular.module('pvpk')\r\n    .factory('Vehicle', ['$resource', 'config', function ($resource, config) {\r\n        return $resource(config.API_URL + '/vehicles', null, {\r\n            'query': {\r\n                url: config.API_URL + '/vehicles',\r\n                method: 'GET',\r\n                isArray: true,\r\n                headers: {\r\n                    'Content-Type': 'application/json',\r\n                    'Accept': 'application/json',\r\n                    'jwt': config.API_TOKEN\r\n                }\r\n            }\r\n        });\r\n    }]);","angular.module('pvpk')\r\n    .controller('infoController', ['$rootScope', '$scope', '$location', 'config', 'Device', 'Vehicle', 'Range', function ($rootScope, $scope, $location, config, Device, Vehicle, Range) {\r\n\r\n        this.$onInit = function () {\r\n            $rootScope.selectDevice = null;\r\n            $scope.showInfoLoading = false;\r\n            $scope.vehicles = [];\r\n            $scope.urlExportCsv = null;\r\n            $scope.directions = [\r\n                {id: undefined, name: 'po směru i proti směru'},\r\n                {id: 1, name: 'po směru'},\r\n                {id: 2, name: 'proti směru'}];\r\n            $scope.isLoadRange = false;\r\n\r\n            Vehicle.query(null, function (data) {\r\n                $scope.vehicles = data;\r\n            }, function (response) {\r\n                $rootScope.graphShow = false;\r\n                console.log('Error api all Vehicles');\r\n                $rootScope.handleErrorResponse(response);\r\n            });\r\n\r\n            $rootScope.$emit('setRangeFromUrl', null);\r\n        };\r\n\r\n        $rootScope.$on('setRangeFromUrl', function (event, args) {\r\n            var params = $location.search();\r\n\r\n            $scope.range = {\r\n                fromDate: moment(params.fromDate, 'YYYY-MM-DD').isValid() ? moment(params.fromDate).toDate() : moment().add(config.DEFAULT_RANGE_DATE_DAY.from, 'd').toDate(),\r\n                toDate: moment(params.toDate, 'YYYY-MM-DD').isValid() ? moment(params.toDate).toDate() : moment().add(config.DEFAULT_RANGE_DATE_DAY.to, 'd').toDate(),\r\n                fromTime: moment(params.fromTime, 'HH:mm').isValid() ? moment(params.fromTime, 'HH:mm').toDate() : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.from}).toDate(),\r\n                toTime: moment(params.toTime, 'HH:mm').isValid() ? moment(params.toTime, 'HH:mm').toDate() : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.to}).toDate(),\r\n                isTime: params.isTime == 0 ? false : true,\r\n                maxDate: $scope.range == null ? null : $scope.range.maxDate,\r\n                minDate: $scope.range == null ? null : $scope.range.minDate\r\n            };\r\n\r\n            if (!$scope.isLoadRange) {\r\n                Range.get(null, function (data) {\r\n                    $scope.range.fromDate = moment.max(moment(data.last_date).add(config.DEFAULT_RANGE_DATE_DAY.from, 'd'), moment(data.first_date)).toDate();\r\n                    $scope.range.toDate = moment.min(moment($scope.range.toDate), moment(data.last_date)).toDate();\r\n                    $scope.range.maxDate = moment(data.last_date).toDate();\r\n                    $scope.range.minDate = moment(data.first_date).toDate();\r\n                    $scope.isLoadRange = true;\r\n                }, function (response) {\r\n                    console.log('Error api get Range');\r\n                    $rootScope.handleErrorResponse(response);\r\n                });\r\n            }\r\n        });\r\n\r\n        $rootScope.$on('infoLocation', function (event, args) {\r\n            $scope.showInfoLoading = true;\r\n\r\n            var params = $location.search();\r\n            params.deviceId = args.id;\r\n            params.direction = args.direction;\r\n            $location.search(params);\r\n\r\n            var range = $scope.getRange();\r\n\r\n            var query = {\r\n                period: range.isTime ? 'time-period' : 'day-period',\r\n                id: args.id,\r\n                direction: args.direction,\r\n                dateFrom: range.fromDate.format('YYYY-MM-DD'),\r\n                dateTo: range.toDate.format('YYYY-MM-DD'),\r\n                timeFrom: range.isTime ? range.fromTime.format('HH:mm') : null,\r\n                timeTo: range.isTime ? range.toTime.format('HH:mm') : null\r\n            };\r\n\r\n            Device.get(query, function (data) {\r\n                $rootScope.selectDevice = data;\r\n                $scope.renderGraph();\r\n                $scope.urlExportCsv = $scope.generateUrlExportCsv(query);\r\n\r\n                $scope.showInfoLoading = false;\r\n            }, function (response) {\r\n                $rootScope.selectDevice = null;\r\n                $scope.showInfoLoading = false;\r\n                console.log('Error api get Devices');\r\n                $rootScope.handleErrorResponse(response);\r\n            });\r\n\r\n        });\r\n\r\n        $scope.generateUrlExportCsv = function (query) {\r\n            var relativeUrl = '/devices/:id/:period/csv?'.replace(':id', query.id).replace(':period', query.period);\r\n            delete query.id;\r\n            delete query.period;\r\n\r\n            var paramsUrl = jQuery.param(query);\r\n            return config.API_URL + relativeUrl + paramsUrl;\r\n        };\r\n\r\n        $scope.changeRange = function () {\r\n            if ($scope.range.fromDate > $scope.range.toDate || ($scope.range.isTime && $scope.range.fromTime >= $scope.range.toTime)) {\r\n                $rootScope.selectDevice.traffics = [];\r\n                return;\r\n            }\r\n\r\n            var range = $scope.getRange();\r\n\r\n            var params = $location.search();\r\n            params.fromDate = range.fromDate.format('YYYY-MM-DD');\r\n            params.toDate = range.toDate.format('YYYY-MM-DD');\r\n            params.fromTime = range.isTime ? range.fromTime.format('HH:mm') : null;\r\n            params.toTime = range.isTime ? range.toTime.format('HH:mm') : null;\r\n            params.isTime = range.isTime ? null : 0;\r\n            $location.search(params);\r\n\r\n            if ($rootScope.selectDevice)\r\n                $rootScope.$emit('infoLocation', {\r\n                    id: $rootScope.selectDevice.id,\r\n                    direction: $rootScope.selectDevice.direction\r\n                });\r\n        };\r\n\r\n        $scope.changeDirection = function () {\r\n\r\n            $rootScope.$emit('infoLocation', {\r\n                id: $rootScope.selectDevice.id,\r\n                direction: $rootScope.selectDevice.direction\r\n            });\r\n        };\r\n\r\n        $scope.getRange = function () {\r\n            return {\r\n                fromDate: moment($scope.range.fromDate).isValid() ? moment($scope.range.fromDate) : moment().add(config.DEFAULT_RANGE_DATE_DAY.from, 'd'),\r\n                toDate: moment($scope.range.toDate).isValid() ? moment($scope.range.toDate) : moment().add(config.DEFAULT_RANGE_DATE_DAY.to, 'd'),\r\n                fromTime: moment($scope.range.fromTime).isValid() ? moment($scope.range.fromTime) : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.from}),\r\n                toTime: moment($scope.range.toTime).isValid() ? moment($scope.range.toTime) : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.to}),\r\n                isTime: $scope.range.isTime ? true : false\r\n            };\r\n        };\r\n\r\n        $scope.renderGraph = function () {\r\n            var color = ['rgba(158, 158, 158, #alpha)', 'rgba(213, 0, 0, #alpha)', 'rgba(0, 123, 255, #alpha)', 'rgba(170, 0, 255, #alpha)',\r\n                'rgba(0, 200, 83, #alpha)', 'rgba(255, 214, 0, #alpha)', 'rgba(255, 109, 0, #alpha)',\r\n                'rgba(174, 234, 0, #alpha)', 'rgba(98, 0, 234, #alpha)', 'rgba(255, 171, 0, #alpha)', 'rgba(100, 221, 23, #alpha)', 'rgba(0, 184, 212, #alpha)'];\r\n\r\n            var labels = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {\r\n                return $scope.range.isTime ? d.timeFrom : moment(d.date, 'YYYY-MM-DD').format('D.M.YYYY');\r\n            }));\r\n\r\n            var useVehiclesIds = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {\r\n                return d.typeVehicleId;\r\n            }));\r\n\r\n            var filterVehicles = jQuery.grep($scope.vehicles, function (n) {\r\n                return useVehiclesIds.indexOf(n.id) >= 0;\r\n            });\r\n\r\n            var datasetsNumberVehicles = [];\r\n            var datasetsAverageSpeed = [];\r\n\r\n            for (var i = 0, vehicle; vehicle = filterVehicles[i]; i++) {\r\n                var datasetNumberVehicles = {\r\n                    label: vehicle.name,\r\n                    backgroundColor: color[vehicle.id].replace(\"#alpha\", \"0.3\"),\r\n                    borderColor: color[vehicle.id].replace(\"#alpha\", \"1\"),\r\n                    borderWidth: 2,\r\n                    data: []\r\n                };\r\n\r\n                var datasetAverageSpeed = {\r\n                    data: [],\r\n                    borderWidth: 2,\r\n                    label: vehicle.name,\r\n                    fill: false,\r\n                    backgroundColor: color[vehicle.id].replace(\"#alpha\", \"0.3\"),\r\n                    borderColor: color[vehicle.id].replace(\"#alpha\", \"1\"),\r\n                    cubicInterpolationMode: 'monotone',\r\n                    pointRadius: 0\r\n                };\r\n\r\n                var l = 0;\r\n                for (var j = 0, traffic; traffic = $rootScope.selectDevice.traffics[j]; j++) {\r\n                    if (($scope.range.isTime && labels[l] !== traffic.timeFrom) || (!$scope.range.isTime && labels[l] !== moment(traffic.date, 'YYYY-MM-DD').format('D.M.YYYY'))) {\r\n                        l++;\r\n                        if (datasetNumberVehicles.data.length < l) {\r\n                            datasetNumberVehicles.data.push(0);\r\n                            datasetAverageSpeed.data.push(0);\r\n                        }\r\n                    }\r\n                    if (traffic.typeVehicleId === vehicle.id) {\r\n                        datasetNumberVehicles.data.push($scope.range.isTime ? traffic.numberVehicleAverage : traffic.numberVehicle);\r\n                        datasetAverageSpeed.data.push(traffic.speedAverage <= 0 ? 0 : traffic.speedAverage);\r\n                    }\r\n                }\r\n                datasetsNumberVehicles.push(datasetNumberVehicles);\r\n                datasetsAverageSpeed.push(datasetAverageSpeed);\r\n            }\r\n\r\n            $rootScope.$emit('renderGraphNumberVehicles', {\r\n                data: {\r\n                    labels: labels,\r\n                    datasets: datasetsNumberVehicles\r\n                }\r\n            });\r\n\r\n            $rootScope.$emit('renderGraphAverageSpeed', {\r\n                data: {\r\n                    labels: labels,\r\n                    datasets: datasetsAverageSpeed\r\n                }\r\n            });\r\n        };\r\n\r\n        $scope.infoClose = function () {\r\n            $rootScope.selectDevice = null;\r\n\r\n            var params = $location.search();\r\n            params.deviceId = null;\r\n            params.direction = null;\r\n            $location.search(params);\r\n\r\n            $rootScope.$emit('setDefaultMap', null);\r\n        };\r\n    }]);\r\n","angular.module('pvpk')\r\n    .controller('mainController', ['$rootScope', '$scope', '$location', '$window', function ($rootScope, $scope, $location, $window) {\r\n\r\n        this.$onInit = function () {\r\n            $scope.showLoadingScreen = true;\r\n        };\r\n\r\n        $window.onload = function () {\r\n            var params = $location.search();\r\n            if (params.deviceId) {\r\n                $rootScope.$emit('activeMarker', {id: params.deviceId});\r\n            }\r\n\r\n            $scope.$apply(function () {\r\n                $scope.showLoadingScreen = false;\r\n            });\r\n        };\r\n\r\n        $rootScope.$on('$locationChangeSuccess', function (event, newUrl, oldUrl) {\r\n            var params = $location.search();\r\n\r\n            if (newUrl !== oldUrl && $scope.historyUrl) {\r\n                if ($scope.historyUrl.q !== $scope.historyUrl.q || $scope.historyUrl.isDirection != params.isDirection) {\r\n                    $rootScope.$emit('setSearchFromUrl', null);\r\n                }\r\n\r\n                if ($scope.historyUrl.fromDate !== params.fromDate || $scope.historyUrl.toDate !== params.toDate ||\r\n                    $scope.historyUrl.fromTime !== params.fromTime || $scope.historyUrl.toTime !== params.toTime) {\r\n                    $rootScope.$emit('setRangeFromUrl', null);\r\n                    if (params.deviceId) {\r\n                        $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});\r\n                    }\r\n                } else if (params.deviceId && ($scope.historyUrl.deviceId !== params.deviceId || $scope.historyUrl.direction !== params.direction)) {\r\n                    $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});\r\n                    $rootScope.$emit('activeMarker', {id: params.deviceId});\r\n                } else if (!params.deviceId && $scope.historyUrl.deviceId) {\r\n                    $rootScope.selectDevice = null;\r\n                    $rootScope.$emit('setDefaultMap', null);\r\n                }\r\n            } else if (params.deviceId) {\r\n                $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});\r\n            }\r\n\r\n            $scope.historyUrl = $location.search();\r\n        });\r\n\r\n        $rootScope.handleErrorResponse = function (response) {\r\n\r\n            var modalError = jQuery('#modalError');\r\n            switch (response.status) {\r\n                case 400:\r\n                    console.log('API ERROR 400');\r\n                    $scope.modalError = {\r\n                        title: 'Neplatný požadavek',\r\n                        body: 'Požadavek nemůže být vyřízen, poněvadž byl syntakticky nesprávně zapsán.',\r\n                        button: 'OK'\r\n                    };\r\n                    modalError.modal('show');\r\n                    break;\r\n                case 401:\r\n                    $scope.modalError = {\r\n                        title: 'Platnost webové aplikace vypršela',\r\n                        body: 'Pro obnovení platnosti stačí stisknout tlačítko Obnovit.',\r\n                        button: 'Obnovit',\r\n                        clickButton: $scope.reloadApp\r\n                    };\r\n                    modalError.modal({backdrop: 'static', keyboard: false});\r\n                    break;\r\n                case 404:\r\n                    console.log('API ERROR 404');\r\n                    $scope.modalError = {title: 'Nenalezen', body: 'Záznam nebyl nalezen.', button: 'OK'};\r\n                    modalError.modal('show');\r\n                    break;\r\n                case 500:\r\n                    console.log('API ERROR 500');\r\n                    $scope.modalError = {title: 'Chyba', body: 'Chyba serveru. Zopakujte akci později.', button: 'OK'};\r\n                    modalError.modal('show');\r\n                    break;\r\n                case -1:\r\n                    console.log('API NOT CONNECTED');\r\n                    $scope.modalError = {\r\n                        title: 'Připojení k internetu',\r\n                        body: 'Nejste připojeni k internetu. Zkontrolujte připojení.',\r\n                        button: 'OK'\r\n                    };\r\n                    modalError.modal('show');\r\n                    break;\r\n                default:\r\n                    console.log('API UNKNOWN ERROR');\r\n                    $scope.modalError = {title: 'Neočekávaná chyba', body: 'Nastala neočekávaná chyba.', button: 'OK'};\r\n                    modalError.modal('show');\r\n                    break;\r\n            }\r\n        };\r\n\r\n        $scope.reloadApp = function () {\r\n            $window.location.reload();\r\n        }\r\n    }]);","angular.module('pvpk')\r\n    .controller('mapController', ['$rootScope', '$scope', 'config', 'Device', function ($rootScope, $scope, config, Device) {\r\n\r\n        this.$onInit = function () {\r\n            $scope.markers = [];\r\n\r\n            $scope.map = new google.maps.Map(document.getElementById('map'), {\r\n                center: config.DEFAULT_POSITION,\r\n                zoom: config.DEFAULT_ZOOM,\r\n                minZoom: config.DEFAULT_ZOOM_MIN,\r\n                zoomControl: true,\r\n                mapTypeControl: false,\r\n                scaleControl: false,\r\n                streetViewControl: false,\r\n                rotateControl: false,\r\n                fullscreenControl: false,\r\n                mapTypeId: google.maps.MapTypeId.ROADMAP\r\n            });\r\n\r\n            Device.query({showDirection: 0}, function (data) {\r\n                for (var i = 0, lctn; lctn = data[i]; i++) {\r\n                    $scope.createMarker(lctn);\r\n                }\r\n            }, function (response) {\r\n                console.log('Error api all Devices');\r\n                $rootScope.handleErrorResponse(response);\r\n            });\r\n        };\r\n\r\n        $scope.createMarker = function (lctn) {\r\n            if (lctn.lat && lctn.lng) {\r\n                var marker = new google.maps.Marker({\r\n                    map: $scope.map,\r\n                    position: {lat: lctn.lat, lng: lctn.lng},\r\n                    title: lctn.name,\r\n                    infoWindow: new google.maps.InfoWindow({\r\n                        content: '<h6 class=\"mb-1\">' + lctn.name + '</h6>'\r\n                        + '<address>' + lctn.street + ', ' + lctn.town + '</address>'\r\n                    }),\r\n                    id: lctn.id\r\n                });\r\n\r\n                marker.addListener('click', function () {\r\n                    $scope.closeInfoWindows();\r\n                    marker.infoWindow.open($scope.map, marker);\r\n                    $rootScope.$emit('infoLocation', {id: lctn.id});\r\n                });\r\n\r\n                $scope.markers.push(marker);\r\n            }\r\n        };\r\n\r\n        $rootScope.$on('activeMarker', function (event, args) {\r\n            for (var i = 0, marker; marker = $scope.markers[i]; i++) {\r\n                if (marker.id && marker.id === args.id && marker.infoWindow) {\r\n                    $scope.map.setCenter(marker.getPosition());\r\n                    $scope.map.setZoom(12);\r\n                    marker.infoWindow.open($scope.map, marker);\r\n                } else {\r\n                    marker.infoWindow.close();\r\n                }\r\n            }\r\n        });\r\n\r\n        $rootScope.$on('setDefaultMap', function (event, args) {\r\n            $scope.map.setCenter(config.DEFAULT_POSITION);\r\n            $scope.map.setZoom(config.DEFAULT_ZOOM);\r\n            $scope.closeInfoWindows();\r\n        });\r\n\r\n        $scope.closeInfoWindows = function () {\r\n            for (var i = 0, marker; marker = $scope.markers[i]; i++) {\r\n                marker.infoWindow.close();\r\n            }\r\n        };\r\n    }]);","angular.module('pvpk')\r\n    .controller('searchController', ['$rootScope', '$scope', '$location', 'config', 'Device', function ($rootScope, $scope, $location, config, Device) {\r\n\r\n        this.$onInit = function () {\r\n            $scope.config = config;\r\n            $scope.locations = [];\r\n            $scope.showSearchLoading = false;\r\n\r\n            $rootScope.$emit('setSearchFromUrl', null);\r\n        };\r\n\r\n        $scope.searchLocations = function () {\r\n            var params = $location.search();\r\n            params.q = $scope.search.q;\r\n            params.isDirection = $scope.search.isDirection ? 1 : null;\r\n            $location.search(params);\r\n\r\n            if (!$scope.search.q || $scope.search.q.length <= 1) {\r\n                $scope.locations = [];\r\n                return;\r\n            }\r\n\r\n            $scope.showSearchLoading = true;\r\n\r\n            Device.query({\r\n                address: $scope.search.q,\r\n                showDirection: $scope.search.isDirection ? 1 : 0\r\n            }, function (data) {\r\n                $scope.locations = data;\r\n                $scope.showSearchLoading = false;\r\n            }, function (response) {\r\n                $scope.showSearchLoading = false;\r\n                console.log('Error api all Devices');\r\n                $rootScope.handleErrorResponse(response);\r\n            });\r\n        };\r\n\r\n        $rootScope.$on('setSearchFromUrl', function (event, args) {\r\n            var params = $location.search();\r\n            $scope.search = {\r\n                q: params.q,\r\n                isDirection: params.isDirection ? !!+params.isDirection : false\r\n            };\r\n            $scope.searchLocations();\r\n        });\r\n\r\n        $scope.selectDevice = function (id, direction) {\r\n            $rootScope.$emit('activeMarker', {id: id});\r\n            $rootScope.$emit('infoLocation', {id: id, direction: direction});\r\n        };\r\n\r\n    }]);","angular.module('pvpk')\r\n    .component('graphAverageSpeed', {\r\n        template: '<div><canvas id=\"graphAverageSpeed\" class=\"graph-size mb-5\"></canvas></div>',\r\n        controller: ['$rootScope', '$scope', function ($rootScope, $scope) {\r\n\r\n            $rootScope.$on('renderGraphAverageSpeed', function (event, args) {\r\n                var canvas = document.getElementById('graphAverageSpeed').getContext('2d');\r\n\r\n                if ($scope.graphLine)\r\n                    $scope.graphLine.destroy();\r\n\r\n                $scope.graphLine = new Chart(canvas, {\r\n                    type: 'line',\r\n                    data: args.data,\r\n                    options: {\r\n                        responsive: true,\r\n                        pointDot: false,\r\n                        legend: {\r\n                            position: 'bottom'\r\n                        },\r\n                        scales: {\r\n                            xAxes: [{\r\n                                ticks: {\r\n                                    autoSkip: true,\r\n                                    maxTicksLimit: 15\r\n                                }\r\n                            }],\r\n                            yAxes: [{\r\n                                scaleLabel: {\r\n                                    display: true,\r\n                                    labelString: 'km/h'\r\n                                },\r\n                                ticks: {\r\n                                    beginAtZero: true,\r\n                                    suggestedMax: 70\r\n                                }\r\n                            }]\r\n                        },\r\n                        tooltips: {\r\n                            mode: 'index',\r\n                            intersect: false,\r\n                            callbacks: {\r\n                                label: function (tooltipItems) {\r\n                                    return tooltipItems.yLabel + ' km/h';\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                });\r\n\r\n            });\r\n\r\n        }]\r\n    });","angular.module('pvpk')\r\n    .component('graphNumberVehicles', {\r\n        template: '<div><canvas id=\"graphNumberVehicles\" class=\"graph-size mb-5\"></canvas></div>',\r\n        controller: ['$rootScope', '$scope', function ($rootScope, $scope) {\r\n\r\n            $rootScope.$on('renderGraphNumberVehicles', function (event, args) {\r\n                var canvasGraphNumberVehicles = document.getElementById('graphNumberVehicles').getContext('2d');\r\n\r\n                if ($scope.graphNumberVehicles)\r\n                    $scope.graphNumberVehicles.destroy();\r\n\r\n                $scope.graphNumberVehicles = new Chart(canvasGraphNumberVehicles, {\r\n                    type: 'bar',\r\n                    data: args.data,\r\n                    options: {\r\n                        responsive: true,\r\n                        onResize: function (chart, size) {\r\n                            chart.options.legend.display = size.height > 240;\r\n                            chart.update();\r\n                        },\r\n                        legend: {\r\n                            position: 'bottom'\r\n                        },\r\n                        scales: {\r\n                            xAxes: [{\r\n                                stacked: true,\r\n                                ticks: {\r\n                                    autoSkip: true,\r\n                                    maxTicksLimit: 15\r\n                                }\r\n                            }],\r\n                            yAxes: [{\r\n                                scaleLabel: {\r\n                                    display: true,\r\n                                    labelString: \"počet vozidel\"\r\n                                },\r\n                                stacked: true\r\n                            }]\r\n                        },\r\n                        tooltips: {\r\n                            mode: 'index',\r\n                            intersect: false\r\n                        }\r\n                    }\r\n                });\r\n\r\n            });\r\n\r\n        }]\r\n    });"]}
frontend/app.webmanifest
1
{
2
  "name": "Průjezd vozidel - Plzeňský kraj",
3
  "short_name": "PVPK",
4
  "start_url": ".",
5
  "display": "standalone",
6
  "background_color": "#fff",
7
  "description": "Zobrazení dat o průjezdu vozidel pro Plzeňský kraj.",
8
  "icons": [{
9
    "src": "./assets/img/favicon.png",
10
    "sizes": "20x18",
11
    "type": "image/png"
12
  }]
13
}
frontend/app/app.config.js
1
angular.module('pvpk')
2
    .constant('config', {
3
        APP_NAME: 'PVPK',
4
        APP_VERSION: '1.3.3',
5
        API_URL: API_URL,
6
        API_TOKEN: API_TOKEN,
7
        DEFAULT_POSITION: {lat: 49.53, lng: 13.3},
8
        DEFAULT_ZOOM: 10,
9
        DEFAULT_ZOOM_MIN: 7,
10
        DEFAULT_RANGE_DATE_DAY: {from: -30, to: -1},
11
        DEFAULT_RANGE_TIME_HOUR: {from: 7, to: 16}
12
    });
frontend/app/app.module.js
1
angular.module('pvpk', ['ngResource']);
frontend/app/controllers/infoController.js
1
angular.module('pvpk')
2
    .controller('infoController', ['$rootScope', '$scope', '$location', 'config', 'Device', 'Vehicle', 'Range', function ($rootScope, $scope, $location, config, Device, Vehicle, Range) {
3

  
4
        this.$onInit = function () {
5
            $rootScope.selectDevice = null;
6
            $scope.showInfoLoading = false;
7
            $scope.vehicles = [];
8
            $scope.urlExportCsv = null;
9
            $scope.directions = [
10
                {id: undefined, name: 'po směru i proti směru'},
11
                {id: 1, name: 'po směru'},
12
                {id: 2, name: 'proti směru'}];
13
            $scope.isLoadRange = false;
14

  
15
            Vehicle.query(null, function (data) {
16
                $scope.vehicles = data;
17
            }, function (response) {
18
                $rootScope.graphShow = false;
19
                console.log('Error api all Vehicles');
20
                $rootScope.handleErrorResponse(response);
21
            });
22

  
23
            $rootScope.$emit('setRangeFromUrl', null);
24
        };
25

  
26
        $rootScope.$on('setRangeFromUrl', function (event, args) {
27
            var params = $location.search();
28

  
29
            $scope.range = {
30
                fromDate: moment(params.fromDate, 'YYYY-MM-DD').isValid() ? moment(params.fromDate).toDate() : moment().add(config.DEFAULT_RANGE_DATE_DAY.from, 'd').toDate(),
31
                toDate: moment(params.toDate, 'YYYY-MM-DD').isValid() ? moment(params.toDate).toDate() : moment().add(config.DEFAULT_RANGE_DATE_DAY.to, 'd').toDate(),
32
                fromTime: moment(params.fromTime, 'HH:mm').isValid() ? moment(params.fromTime, 'HH:mm').toDate() : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.from}).toDate(),
33
                toTime: moment(params.toTime, 'HH:mm').isValid() ? moment(params.toTime, 'HH:mm').toDate() : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.to}).toDate(),
34
                isTime: params.isTime == 0 ? false : true,
35
                maxDate: $scope.range == null ? null : $scope.range.maxDate,
36
                minDate: $scope.range == null ? null : $scope.range.minDate
37
            };
38

  
39
            if (!$scope.isLoadRange) {
40
                Range.get(null, function (data) {
41
                    $scope.range.fromDate = moment.max(moment(data.last_date).add(config.DEFAULT_RANGE_DATE_DAY.from, 'd'), moment(data.first_date)).toDate();
42
                    $scope.range.toDate = moment.min(moment($scope.range.toDate), moment(data.last_date)).toDate();
43
                    $scope.range.maxDate = moment(data.last_date).toDate();
44
                    $scope.range.minDate = moment(data.first_date).toDate();
45
                    $scope.isLoadRange = true;
46
                }, function (response) {
47
                    console.log('Error api get Range');
48
                    $rootScope.handleErrorResponse(response);
49
                });
50
            }
51
        });
52

  
53
        $rootScope.$on('infoLocation', function (event, args) {
54
            $scope.showInfoLoading = true;
55

  
56
            var params = $location.search();
57
            params.deviceId = args.id;
58
            params.direction = args.direction;
59
            $location.search(params);
60

  
61
            var range = $scope.getRange();
62

  
63
            var query = {
64
                period: range.isTime ? 'time-period' : 'day-period',
65
                id: args.id,
66
                direction: args.direction,
67
                dateFrom: range.fromDate.format('YYYY-MM-DD'),
68
                dateTo: range.toDate.format('YYYY-MM-DD'),
69
                timeFrom: range.isTime ? range.fromTime.format('HH:mm') : null,
70
                timeTo: range.isTime ? range.toTime.format('HH:mm') : null
71
            };
72

  
73
            Device.get(query, function (data) {
74
                $rootScope.selectDevice = data;
75
                $scope.renderGraph();
76
                $scope.urlExportCsv = $scope.generateUrlExportCsv(query);
77

  
78
                $scope.showInfoLoading = false;
79
            }, function (response) {
80
                $rootScope.selectDevice = null;
81
                $scope.showInfoLoading = false;
82
                console.log('Error api get Devices');
83
                $rootScope.handleErrorResponse(response);
84
            });
85

  
86
        });
87

  
88
        $scope.generateUrlExportCsv = function (query) {
89
            var relativeUrl = '/devices/:id/:period/csv?'.replace(':id', query.id).replace(':period', query.period);
90
            delete query.id;
91
            delete query.period;
92

  
93
            var paramsUrl = jQuery.param(query);
94
            return config.API_URL + relativeUrl + paramsUrl;
95
        };
96

  
97
        $scope.changeRange = function () {
98
            if ($scope.range.fromDate > $scope.range.toDate || ($scope.range.isTime && $scope.range.fromTime >= $scope.range.toTime)) {
99
                $rootScope.selectDevice.traffics = [];
100
                return;
101
            }
102

  
103
            var range = $scope.getRange();
104

  
105
            var params = $location.search();
106
            params.fromDate = range.fromDate.format('YYYY-MM-DD');
107
            params.toDate = range.toDate.format('YYYY-MM-DD');
108
            params.fromTime = range.isTime ? range.fromTime.format('HH:mm') : null;
109
            params.toTime = range.isTime ? range.toTime.format('HH:mm') : null;
110
            params.isTime = range.isTime ? null : 0;
111
            $location.search(params);
112

  
113
            if ($rootScope.selectDevice)
114
                $rootScope.$emit('infoLocation', {
115
                    id: $rootScope.selectDevice.id,
116
                    direction: $rootScope.selectDevice.direction
117
                });
118
        };
119

  
120
        $scope.changeDirection = function () {
121

  
122
            $rootScope.$emit('infoLocation', {
123
                id: $rootScope.selectDevice.id,
124
                direction: $rootScope.selectDevice.direction
125
            });
126
        };
127

  
128
        $scope.getRange = function () {
129
            return {
130
                fromDate: moment($scope.range.fromDate).isValid() ? moment($scope.range.fromDate) : moment().add(config.DEFAULT_RANGE_DATE_DAY.from, 'd'),
131
                toDate: moment($scope.range.toDate).isValid() ? moment($scope.range.toDate) : moment().add(config.DEFAULT_RANGE_DATE_DAY.to, 'd'),
132
                fromTime: moment($scope.range.fromTime).isValid() ? moment($scope.range.fromTime) : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.from}),
133
                toTime: moment($scope.range.toTime).isValid() ? moment($scope.range.toTime) : moment({hour: config.DEFAULT_RANGE_TIME_HOUR.to}),
134
                isTime: $scope.range.isTime ? true : false
135
            };
136
        };
137

  
138
        $scope.renderGraph = function () {
139
            var color = ['rgba(158, 158, 158, #alpha)', 'rgba(213, 0, 0, #alpha)', 'rgba(0, 123, 255, #alpha)', 'rgba(170, 0, 255, #alpha)',
140
                'rgba(0, 200, 83, #alpha)', 'rgba(255, 214, 0, #alpha)', 'rgba(255, 109, 0, #alpha)',
141
                'rgba(174, 234, 0, #alpha)', 'rgba(98, 0, 234, #alpha)', 'rgba(255, 171, 0, #alpha)', 'rgba(100, 221, 23, #alpha)', 'rgba(0, 184, 212, #alpha)'];
142

  
143
            var labels = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {
144
                return $scope.range.isTime ? d.timeFrom : moment(d.date, 'YYYY-MM-DD').format('D.M.YYYY');
145
            }));
146

  
147
            var useVehiclesIds = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {
148
                return d.typeVehicleId;
149
            }));
150

  
151
            var filterVehicles = jQuery.grep($scope.vehicles, function (n) {
152
                return useVehiclesIds.indexOf(n.id) >= 0;
153
            });
154

  
155
            var datasetsNumberVehicles = [];
156
            var datasetsAverageSpeed = [];
157

  
158
            for (var i = 0, vehicle; vehicle = filterVehicles[i]; i++) {
159
                var datasetNumberVehicles = {
160
                    label: vehicle.name,
161
                    backgroundColor: color[vehicle.id].replace("#alpha", "0.3"),
162
                    borderColor: color[vehicle.id].replace("#alpha", "1"),
163
                    borderWidth: 2,
164
                    data: []
165
                };
166

  
167
                var datasetAverageSpeed = {
168
                    data: [],
169
                    borderWidth: 2,
170
                    label: vehicle.name,
171
                    fill: false,
172
                    backgroundColor: color[vehicle.id].replace("#alpha", "0.3"),
173
                    borderColor: color[vehicle.id].replace("#alpha", "1"),
174
                    cubicInterpolationMode: 'monotone',
175
                    pointRadius: 0
176
                };
177

  
178
                var l = 0;
179
                for (var j = 0, traffic; traffic = $rootScope.selectDevice.traffics[j]; j++) {
180
                    if (($scope.range.isTime && labels[l] !== traffic.timeFrom) || (!$scope.range.isTime && labels[l] !== moment(traffic.date, 'YYYY-MM-DD').format('D.M.YYYY'))) {
181
                        l++;
182
                        if (datasetNumberVehicles.data.length < l) {
183
                            datasetNumberVehicles.data.push(0);
184
                            datasetAverageSpeed.data.push(0);
185
                        }
186
                    }
187
                    if (traffic.typeVehicleId === vehicle.id) {
188
                        datasetNumberVehicles.data.push($scope.range.isTime ? traffic.numberVehicleAverage : traffic.numberVehicle);
189
                        datasetAverageSpeed.data.push(traffic.speedAverage <= 0 ? 0 : traffic.speedAverage);
190
                    }
191
                }
192
                datasetsNumberVehicles.push(datasetNumberVehicles);
193
                datasetsAverageSpeed.push(datasetAverageSpeed);
194
            }
195

  
196
            $rootScope.$emit('renderGraphNumberVehicles', {
197
                data: {
198
                    labels: labels,
199
                    datasets: datasetsNumberVehicles
200
                }
201
            });
202

  
203
            $rootScope.$emit('renderGraphAverageSpeed', {
204
                data: {
205
                    labels: labels,
206
                    datasets: datasetsAverageSpeed
207
                }
208
            });
209
        };
210

  
211
        $scope.infoClose = function () {
212
            $rootScope.selectDevice = null;
213

  
214
            var params = $location.search();
215
            params.deviceId = null;
216
            params.direction = null;
217
            $location.search(params);
218

  
219
            $rootScope.$emit('setDefaultMap', null);
220
        };
221
    }]);
frontend/app/controllers/mainController.js
1
angular.module('pvpk')
2
    .controller('mainController', ['$rootScope', '$scope', '$location', '$window', function ($rootScope, $scope, $location, $window) {
3

  
4
        this.$onInit = function () {
5
            $scope.showLoadingScreen = true;
6
        };
7

  
8
        $window.onload = function () {
9
            var params = $location.search();
10
            if (params.deviceId) {
11
                $rootScope.$emit('activeMarker', {id: params.deviceId});
12
            }
13

  
14
            $scope.$apply(function () {
15
                $scope.showLoadingScreen = false;
16
            });
17
        };
18

  
19
        $rootScope.$on('$locationChangeSuccess', function (event, newUrl, oldUrl) {
20
            var params = $location.search();
21

  
22
            if (newUrl !== oldUrl && $scope.historyUrl) {
23
                if ($scope.historyUrl.q !== $scope.historyUrl.q || $scope.historyUrl.isDirection != params.isDirection) {
24
                    $rootScope.$emit('setSearchFromUrl', null);
25
                }
26

  
27
                if ($scope.historyUrl.fromDate !== params.fromDate || $scope.historyUrl.toDate !== params.toDate ||
28
                    $scope.historyUrl.fromTime !== params.fromTime || $scope.historyUrl.toTime !== params.toTime) {
29
                    $rootScope.$emit('setRangeFromUrl', null);
30
                    if (params.deviceId) {
31
                        $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
32
                    }
33
                } else if (params.deviceId && ($scope.historyUrl.deviceId !== params.deviceId || $scope.historyUrl.direction !== params.direction)) {
34
                    $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
35
                    $rootScope.$emit('activeMarker', {id: params.deviceId});
36
                } else if (!params.deviceId && $scope.historyUrl.deviceId) {
37
                    $rootScope.selectDevice = null;
38
                    $rootScope.$emit('setDefaultMap', null);
39
                }
40
            } else if (params.deviceId) {
41
                $rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
42
            }
43

  
44
            $scope.historyUrl = $location.search();
45
        });
46

  
47
        $rootScope.handleErrorResponse = function (response) {
48

  
49
            var modalError = jQuery('#modalError');
50
            switch (response.status) {
51
                case 400:
52
                    console.log('API ERROR 400');
53
                    $scope.modalError = {
54
                        title: 'Neplatný požadavek',
55
                        body: 'Požadavek nemůže být vyřízen, poněvadž byl syntakticky nesprávně zapsán.',
56
                        button: 'OK'
57
                    };
58
                    modalError.modal('show');
59
                    break;
60
                case 401:
61
                    $scope.modalError = {
62
                        title: 'Platnost webové aplikace vypršela',
63
                        body: 'Pro obnovení platnosti stačí stisknout tlačítko Obnovit.',
64
                        button: 'Obnovit',
65
                        clickButton: $scope.reloadApp
66
                    };
67
                    modalError.modal({backdrop: 'static', keyboard: false});
68
                    break;
69
                case 404:
70
                    console.log('API ERROR 404');
71
                    $scope.modalError = {title: 'Nenalezen', body: 'Záznam nebyl nalezen.', button: 'OK'};
72
                    modalError.modal('show');
73
                    break;
74
                case 500:
75
                    console.log('API ERROR 500');
76
                    $scope.modalError = {title: 'Chyba', body: 'Chyba serveru. Zopakujte akci později.', button: 'OK'};
77
                    modalError.modal('show');
78
                    break;
79
                case -1:
80
                    console.log('API NOT CONNECTED');
81
                    $scope.modalError = {
82
                        title: 'Připojení k internetu',
83
                        body: 'Nejste připojeni k internetu. Zkontrolujte připojení.',
84
                        button: 'OK'
85
                    };
86
                    modalError.modal('show');
87
                    break;
88
                default:
89
                    console.log('API UNKNOWN ERROR');
90
                    $scope.modalError = {title: 'Neočekávaná chyba', body: 'Nastala neočekávaná chyba.', button: 'OK'};
91
                    modalError.modal('show');
92
                    break;
93
            }
94
        };
95

  
96
        $scope.reloadApp = function () {
97
            $window.location.reload();
98
        }
99
    }]);
frontend/app/controllers/mapController.js
1
angular.module('pvpk')
2
    .controller('mapController', ['$rootScope', '$scope', 'config', 'Device', function ($rootScope, $scope, config, Device) {
3

  
4
        this.$onInit = function () {
5
            $scope.markers = [];
6

  
7
            $scope.map = new google.maps.Map(document.getElementById('map'), {
8
                center: config.DEFAULT_POSITION,
9
                zoom: config.DEFAULT_ZOOM,
10
                minZoom: config.DEFAULT_ZOOM_MIN,
11
                zoomControl: true,
12
                mapTypeControl: false,
13
                scaleControl: false,
14
                streetViewControl: false,
15
                rotateControl: false,
16
                fullscreenControl: false,
17
                mapTypeId: google.maps.MapTypeId.ROADMAP
18
            });
19

  
20
            Device.query({showDirection: 0}, function (data) {
21
                for (var i = 0, lctn; lctn = data[i]; i++) {
22
                    $scope.createMarker(lctn);
23
                }
24
            }, function (response) {
25
                console.log('Error api all Devices');
26
                $rootScope.handleErrorResponse(response);
27
            });
28
        };
29

  
30
        $scope.createMarker = function (lctn) {
31
            if (lctn.lat && lctn.lng) {
32
                var marker = new google.maps.Marker({
33
                    map: $scope.map,
34
                    position: {lat: lctn.lat, lng: lctn.lng},
35
                    title: lctn.name,
36
                    infoWindow: new google.maps.InfoWindow({
37
                        content: '<h6 class="mb-1">' + lctn.name + '</h6>'
38
                        + '<address>' + lctn.street + ', ' + lctn.town + '</address>'
39
                    }),
40
                    id: lctn.id
41
                });
42

  
43
                marker.addListener('click', function () {
44
                    $scope.closeInfoWindows();
45
                    marker.infoWindow.open($scope.map, marker);
46
                    $rootScope.$emit('infoLocation', {id: lctn.id});
47
                });
48

  
49
                $scope.markers.push(marker);
50
            }
51
        };
52

  
53
        $rootScope.$on('activeMarker', function (event, args) {
54
            for (var i = 0, marker; marker = $scope.markers[i]; i++) {
55
                if (marker.id && marker.id === args.id && marker.infoWindow) {
56
                    $scope.map.setCenter(marker.getPosition());
57
                    $scope.map.setZoom(12);
58
                    marker.infoWindow.open($scope.map, marker);
59
                } else {
60
                    marker.infoWindow.close();
61
                }
62
            }
63
        });
64

  
65
        $rootScope.$on('setDefaultMap', function (event, args) {
66
            $scope.map.setCenter(config.DEFAULT_POSITION);
67
            $scope.map.setZoom(config.DEFAULT_ZOOM);
68
            $scope.closeInfoWindows();
69
        });
70

  
71
        $scope.closeInfoWindows = function () {
72
            for (var i = 0, marker; marker = $scope.markers[i]; i++) {
73
                marker.infoWindow.close();
74
            }
75
        };
76
    }]);
frontend/app/controllers/searchController.js
1
angular.module('pvpk')
2
    .controller('searchController', ['$rootScope', '$scope', '$location', 'config', 'Device', function ($rootScope, $scope, $location, config, Device) {
3

  
4
        this.$onInit = function () {
5
            $scope.config = config;
6
            $scope.locations = [];
7
            $scope.showSearchLoading = false;
8

  
9
            $rootScope.$emit('setSearchFromUrl', null);
10
        };
11

  
12
        $scope.searchLocations = function () {
13
            var params = $location.search();
14
            params.q = $scope.search.q;
15
            params.isDirection = $scope.search.isDirection ? 1 : null;
16
            $location.search(params);
17

  
18
            if (!$scope.search.q || $scope.search.q.length <= 1) {
19
                $scope.locations = [];
20
                return;
21
            }
22

  
23
            $scope.showSearchLoading = true;
24

  
25
            Device.query({
26
                address: $scope.search.q,
27
                showDirection: $scope.search.isDirection ? 1 : 0
28
            }, function (data) {
29
                $scope.locations = data;
30
                $scope.showSearchLoading = false;
31
            }, function (response) {
32
                $scope.showSearchLoading = false;
33
                console.log('Error api all Devices');
34
                $rootScope.handleErrorResponse(response);
35
            });
36
        };
37

  
38
        $rootScope.$on('setSearchFromUrl', function (event, args) {
39
            var params = $location.search();
40
            $scope.search = {
41
                q: params.q,
42
                isDirection: params.isDirection ? !!+params.isDirection : false
43
            };
44
            $scope.searchLocations();
45
        });
46

  
47
        $scope.selectDevice = function (id, direction) {
48
            $rootScope.$emit('activeMarker', {id: id});
49
            $rootScope.$emit('infoLocation', {id: id, direction: direction});
50
        };
51

  
52
    }]);
frontend/app/directives/graphAverageSpeed.js
1
angular.module('pvpk')
2
    .component('graphAverageSpeed', {
3
        template: '<div><canvas id="graphAverageSpeed" class="graph-size mb-5"></canvas></div>',
4
        controller: ['$rootScope', '$scope', function ($rootScope, $scope) {
5

  
6
            $rootScope.$on('renderGraphAverageSpeed', function (event, args) {
7
                var canvas = document.getElementById('graphAverageSpeed').getContext('2d');
8

  
9
                if ($scope.graphLine)
10
                    $scope.graphLine.destroy();
11

  
12
                $scope.graphLine = new Chart(canvas, {
13
                    type: 'line',
14
                    data: args.data,
15
                    options: {
16
                        responsive: true,
17
                        pointDot: false,
18
                        legend: {
19
                            position: 'bottom'
20
                        },
21
                        scales: {
22
                            xAxes: [{
23
                                ticks: {
24
                                    autoSkip: true,
25
                                    maxTicksLimit: 15
26
                                }
27
                            }],
28
                            yAxes: [{
29
                                scaleLabel: {
30
                                    display: true,
31
                                    labelString: 'km/h'
32
                                },
33
                                ticks: {
34
                                    beginAtZero: true,
35
                                    suggestedMax: 70
36
                                }
37
                            }]
38
                        },
39
                        tooltips: {
40
                            mode: 'index',
41
                            intersect: false,
42
                            callbacks: {
43
                                label: function (tooltipItems) {
44
                                    return tooltipItems.yLabel + ' km/h';
45
                                }
46
                            }
47
                        }
48
                    }
49
                });
50

  
51
            });
52

  
53
        }]
54
    });
frontend/app/directives/graphNumberVehicles.js
1
angular.module('pvpk')
2
    .component('graphNumberVehicles', {
3
        template: '<div><canvas id="graphNumberVehicles" class="graph-size mb-5"></canvas></div>',
4
        controller: ['$rootScope', '$scope', function ($rootScope, $scope) {
5

  
6
            $rootScope.$on('renderGraphNumberVehicles', function (event, args) {
7
                var canvasGraphNumberVehicles = document.getElementById('graphNumberVehicles').getContext('2d');
8

  
9
                if ($scope.graphNumberVehicles)
10
                    $scope.graphNumberVehicles.destroy();
11

  
12
                $scope.graphNumberVehicles = new Chart(canvasGraphNumberVehicles, {
13
                    type: 'bar',
14
                    data: args.data,
15
                    options: {
16
                        responsive: true,
17
                        onResize: function (chart, size) {
18
                            chart.options.legend.display = size.height > 240;
19
                            chart.update();
20
                        },
21
                        legend: {
22
                            position: 'bottom'
23
                        },
24
                        scales: {
25
                            xAxes: [{
26
                                stacked: true,
27
                                ticks: {
28
                                    autoSkip: true,
29
                                    maxTicksLimit: 15
30
                                }
31
                            }],
32
                            yAxes: [{
33
                                scaleLabel: {
34
                                    display: true,
35
                                    labelString: "počet vozidel"
36
                                },
37
                                stacked: true
38
                            }]
39
                        },
40
                        tooltips: {
41
                            mode: 'index',
42
                            intersect: false
43
                        }
44
                    }
45
                });
46

  
47
            });
... Rozdílový soubor je zkrácen, protože jeho délka přesahuje max. limit.

Také k dispozici: Unified diff