From 67b5040175a6c282be515dfb61e30540f22d0333 Mon Sep 17 00:00:00 2001 From: PliablePixels Date: Wed, 5 Aug 2015 08:10:28 -0400 Subject: Experimental timeline --- www/js/TimelineCtrl.js | 359 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 www/js/TimelineCtrl.js (limited to 'www/js/TimelineCtrl.js') diff --git a/www/js/TimelineCtrl.js b/www/js/TimelineCtrl.js new file mode 100644 index 00000000..adeed4a9 --- /dev/null +++ b/www/js/TimelineCtrl.js @@ -0,0 +1,359 @@ +/* jshint -W041 */ +/* jshint -W083 */ +/*This is for the loop closure I am using in line 143 */ +/* jslint browser: true*/ +/* global vis,cordova,StatusBar,angular,console,moment */ + +// This controller generates a graph for events +// the main function is generateChart. I call generate chart with required parameters +// from the template file + +angular.module('zmApp.controllers').controller('zmApp.TimelineCtrl', ['$ionicPlatform', '$scope', 'zm', 'ZMDataModel', '$ionicSideMenuDelegate', '$rootScope', '$http', '$q','message','$state', '$ionicLoading', '$ionicPopover', function ($ionicPlatform, $scope, zm, ZMDataModel, $ionicSideMenuDelegate, $rootScope, $http, $q,message, $state, $ionicLoading, $ionicPopover) { + + console.log("Inside Timeline controller"); + $scope.openMenu = function () { + $ionicSideMenuDelegate.toggleLeft(); + }; + + $scope.leftButtons = [{ + type: 'button-icon icon ion-navicon', + tap: function (e) { + $scope.toggleMenu(); + } + }]; + + $scope.prettify = function (str) { + return moment(str).format('MMMM Do YYYY, h:mm:ssa'); + }; + + + //------------------------------------------------- + // Make sure we delete the timeline + // This may be redundant as the root view gets + // destroyed but no harm + //------------------------------------------------- + $scope.$on('$ionicView.leave', function () { + //console.log("**Destroying Timeline"); + //timeline.destroy(); + }); + + $scope.$on('$ionicView.afterEnter', function () { + console.log ("***AFTER ENTER"); + + if ($rootScope.customTimelineRange) + { + console.log ("***** CUSTOM RANGE"); + if (moment($rootScope.fromString).isValid() && + moment($rootScope.toString).isValid()) + { + console.log ("FROM & TO IS CUSTOM"); + fromDate = $rootScope.fromString; + toDate = $rootScope.toString; + $scope.fromDate = fromDate; + $scope.toDate = toDate; + drawGraph(fromDate, toDate, maxItems); + } + else + { + console.log ("FROM & TO IS CUSTOM INVALID"); + } + } + }); + + //------------------------------------------------- + // Controller main + //------------------------------------------------- + + // Make sure sliding for menu is disabled so it + // does not interfere with graph panning + $ionicSideMenuDelegate.canDragContent(false); + + // fromDate and toDate will be used to plot the range for the graph + // We start in day mode + var fromDate = moment().startOf('day').format("YYYY-MM-DD HH:mm:ss"); + var toDate = moment().endOf('day').format("YYYY-MM-DD HH:mm:ss"); + + + + $scope.fromDate = fromDate; + $scope.toDate = toDate; + + var maxItems = 200; + + //flat colors for graph - https://flatuicolors.com + var colors = ['#3498db', '#83adb5', '#c7bbc9', '#f39c12', '#bfb5b2', '#e74c3c']; + + var container ; + container = angular.element(document.getElementById('visualization')); + var timeline=""; + + $scope.monitors = message; + + $ionicPopover.fromTemplateUrl('templates/timeline-popover.html', { + scope: $scope, + }).then(function(popover) { + $scope.popover = popover; + }); + + + //drawGraph(fromDate, toDate,maxItems); + dummyDrawGraph(fromDate, toDate,maxItems); + + //------------------------------------------------- + // Rest graph to sane state after you went + // wild zooming and panning :-) + //------------------------------------------------- + $scope.fit = function () { + timeline.fit(); + }; + + //------------------------------------------------- + // Called with day/week/month + // so we can redraw the graph + //------------------------------------------------- + + $scope.buttonClicked = function(index) + { + //console.log (index); + if (index == 0) //month + { + ZMDataModel.zmLog ("Month view"); + $rootScope.customTimelineRange = false; + + toDate = moment().format("YYYY-MM-DD HH:mm:ss"); + fromDate = moment().subtract(1,'month').startOf('day').format("YYYY-MM-DD HH:mm:ss"); + $scope.fromDate = fromDate; + $scope.toDate = toDate; + drawGraph(fromDate, toDate,maxItems); + } + else if (index == 1) //week + { + $rootScope.customTimelineRange = false; + ZMDataModel.zmLog("Week view"); + toDate = moment().format("YYYY-MM-DD HH:mm:ss"); + fromDate = moment().subtract(1,'week').startOf('day').format("YYYY-MM-DD HH:mm:ss"); + $scope.fromDate = fromDate; + $scope.toDate = toDate; + drawGraph(fromDate, toDate,maxItems); + } + else if (index==2) //day + { + $rootScope.customTimelineRange = false; + ZMDataModel.zmLog ("Day view"); + toDate = moment().format("YYYY-MM-DD HH:mm:ss"); + fromDate = moment().subtract(1,'day').startOf('day').format("YYYY-MM-DD HH:mm:ss"); + $scope.fromDate = fromDate; + $scope.toDate = toDate; + drawGraph(fromDate, toDate,maxItems); + } + + else // custom + { + $rootScope.customTimelineRange = true; + $state.go('events-date-time-filter'); + } + + }; + + // ------------------------------------------------------ + // Draws a random graph from Vis timeline performance + // ----------------------------------------------------- + function dummyDrawGraph(fromDate, toDate, count) + { + if (timeline) timeline.destroy(); + + var groups = new vis.DataSet([ + {id: 1, content: 'Truck 1'}, + {id: 2, content: 'Truck 2'}, + {id: 3, content: 'Truck 3'}, + {id: 4, content: 'Truck 4'} + ]); + + // create items + var items = new vis.DataSet(); + + var order = 1; + var truck = 1; + for (var j = 0; j < 4; j++) { + var date = new Date(); + for (var i = 0; i < count/4; i++) { + date.setHours(date.getHours() + 4 * (Math.random() < 0.2)); + var start = new Date(date); + + date.setHours(date.getHours() + 2 + Math.floor(Math.random()*4)); + var end = new Date(date); + + items.add({ + id: order, + group: truck, + start: start, + end: end, + content: 'Order ' + order + }); + + order++; + } + truck++; + } + + // specify options + var options = { + stack: false, + start: new Date(), + end: new Date(1000*60*60*24 + (new Date()).valueOf()), + editable: false, + margin: { + item: 10, // minimal margin between items + axis: 5 // minimal margin between items and the axis + }, + orientation: 'top' + }; + timeline = new vis.Timeline(container[0], null, options); + timeline.setGroups(groups); + timeline.setItems(items); + } + + + //------------------------------------------------- + // This function draws the graph + // So far struggling with mobile perf + // Observations so far: + // a) Just about acceptable performance with 100 items + // b) Sometimes on panning CPU gets locked at 99% + // for over 3-4 seconds + //------------------------------------------------- + + function drawGraph(fromDate, toDate, count) { + + $ionicLoading.show({ + template: "Loading graph...", + animation: 'fade-in', + showBackdrop: true, + maxWidth: 200, + showDelay: 0, + duration: zm.loadingTimeout, //specifically for Android - http seems to get stuck at times + }); + + if (timeline) timeline.destroy(); + + + + var groups = new vis.DataSet(); + var graphData = new vis.DataSet(); + //console.log ("AFTER VIS"); + + + var options = { + // autoResize: false, // true makes it much slower + //configure: true, + editable: false, + // moveable: true, + // zoomable: true, + start:fromDate, + end:toDate, + orientation: 'top', + min: fromDate, + max: toDate, + //width:'90%', + + zoomMin: 1 * 60 * 1000, // 1 min + stack: false, + format: { + minorLabels: { + minute: "hh:mm a", + hour: 'hh:mm a', + second: 's', + }, + majorLabels: { + second: "D MMM hh:mm a", + } + }, + + }; + + var graphIndex = 1; // will be used for graph ID + + ZMDataModel.getEventsPages(0, fromDate, toDate) + .then(function (data) { + var pages = parseInt(data.pageCount); + var itemsPerPage = parseInt(data.limit); + var iterCount; + + // The graph seems to get very slow + // even with 200 items. My data comes in pages from + // the server - so to make sure I don't exceed 200 items + // I figure out how many items the server returns per API page + // and divide the # of items I want (currently 200) with # of items per page + // So iterCount is the # of HTTP calls I need to make + iterCount = Math.round(count/itemsPerPage); + console.log("I will make " + iterCount + " HTTP Requests "); + + // I've restructured this part. I was initially using vis DataSets + // for dynamic binding which was easier, but due to performance reasons + // I am waiting for the full data to load before I draw + var promises = []; + while ((pages > 0) && (iterCount >0)) { + var promise = ZMDataModel.getEvents(0, pages, "none", fromDate, toDate); + promises.push(promise); + pages--; + iterCount--; + + } + + $q.all(promises) + .then(function (data) { + + + // create groups + for (var g=0; g<$scope.monitors.length; g++) + { + groups.add({ + id: $scope.monitors[g].Monitor.Id, + content: ZMDataModel.getMonitorName($scope.monitors[g].Monitor.Id) + }); + } + for (var j = 0; j < data.length; j++) { + var myevents = data[j]; + + + for (var i = 0; i < myevents.length; i++) { + + graphData.add({ + id: graphIndex, + content: '', + start: myevents[i].Event.StartTime, + end: myevents[i].Event.EndTime, + group: myevents[i].Event.MonitorId, + //type: "range", + style: "background-color:" + colors[parseInt(myevents[i].Event.MonitorId) % colors.length] + ";border-color:" + colors[parseInt(myevents[i].Event.MonitorId) % colors.length], + // title: "Hello" + + }); + + graphIndex++; + + } + } + + timeline = new vis.Timeline(container[0], null, options); + timeline.setItems(graphData); + timeline.setGroups(groups); + timeline.fit(); + $ionicLoading.hide(); + }); // get Events + }); + } + + + function arrayObjectIndexOf(myArray, searchTerm, property) { + for (var i = 0, len = myArray.length; i < len; i++) { + if (myArray[i][property] === searchTerm) return i; + } + return -1; + } + + + + + +}]); -- cgit v1.2.3