| @ -1,47 +0,0 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="utf-8"> | |||||
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <title>Feature Requests</title> | |||||
| <!-- Bootstrap --> | |||||
| <link href="css/bootstrap.min.css" rel="stylesheet"> | |||||
| <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> | |||||
| <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> | |||||
| <!--[if lt IE 9]> | |||||
| <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> | |||||
| <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> | |||||
| <![endif]--> | |||||
| <style> | |||||
| body { | |||||
| padding:10px; | |||||
| } | |||||
| div.textHTMLContent { | |||||
| display:block; | |||||
| width:800px; | |||||
| } | |||||
| </style> | |||||
| </head> | |||||
| <body> | |||||
| <h1>Showcase projects</h1> | |||||
| <div class="textHTMLContent"> | |||||
| - MIDAS | |||||
| - Git | |||||
| - Cool examples (?) | |||||
| </div> | |||||
| <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> | |||||
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> | |||||
| <!-- Include all compiled plugins (below), or include individual files as needed --> | |||||
| <script src="js/bootstrap.min.js"></script> | |||||
| </body> | |||||
| </html> | |||||
| @ -0,0 +1,93 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="utf-8"> | |||||
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <title>Feature Requests</title> | |||||
| <!-- Bootstrap --> | |||||
| <link href="css/bootstrap.min.css" rel="stylesheet"> | |||||
| <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> | |||||
| <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> | |||||
| <!--[if lt IE 9]> | |||||
| <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> | |||||
| <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> | |||||
| <![endif]--> | |||||
| <style> | |||||
| a { | |||||
| text-decoration: none; | |||||
| } | |||||
| body { | |||||
| padding:10px; | |||||
| font-family: Lustria,Georgia,Times,"Times New Roman",serif !important; | |||||
| } | |||||
| div.textHTMLContent { | |||||
| display:block; | |||||
| width:970px; | |||||
| } | |||||
| img.showcase { | |||||
| position:relative; | |||||
| border: 1px solid #dddddd; | |||||
| border-radius:20px; | |||||
| width:900px; | |||||
| height:350px; | |||||
| } | |||||
| div.description{ | |||||
| width:880px; | |||||
| position:relative; | |||||
| border-radius:20px; | |||||
| padding:20px; | |||||
| text-decoration: none; | |||||
| color:#ffffff; | |||||
| background-color:#064880; | |||||
| margin-top:-80px; | |||||
| margin-left:20px; | |||||
| z-index:2; | |||||
| box-shadow: 0px 0px 8px #000000; | |||||
| } | |||||
| div.descriptionHeader { | |||||
| font-size:25px; | |||||
| font-weight:bold; | |||||
| padding-bottom:5px; | |||||
| } | |||||
| </style> | |||||
| </head> | |||||
| <body> | |||||
| <h1>Showcase projects</h1> | |||||
| <div class="textHTMLContent"> | |||||
| <a href="./projects/midas/index.html"> | |||||
| <div class="showcase"> | |||||
| <img src="./images/midas.png" class="showcase"> | |||||
| <div class="description"> | |||||
| <div class="descriptionHeader"> | |||||
| M.I.D.A.S. : Manufacturing Incident Detection Agent Solution | |||||
| </div> | |||||
| <div class="descriptionContent"> | |||||
| MIDAS collects statistics of incidents during the production process. | |||||
| It analyses the manufacturing process and handles disruptive events during the ramp-up phase. | |||||
| Almende developed the software tool especially for this project, based on the inhouse toolchain, | |||||
| specifically Eve, AIM and vis.js. | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </a> | |||||
| </div> | |||||
| <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> | |||||
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> | |||||
| <!-- Include all compiled plugins (below), or include individual files as needed --> | |||||
| <script src="js/bootstrap.min.js"></script> | |||||
| </body> | |||||
| </html> | |||||
| @ -0,0 +1,216 @@ | |||||
| 'use strict'; | |||||
| function AgentGenerator(id) { | |||||
| // execute super constructor | |||||
| eve.Agent.call(this, id); | |||||
| this.rpc = this.loadModule('rpc', this.rpcFunctions); | |||||
| var me = this; | |||||
| this.amountOfEvents = 0; | |||||
| this.eventNumber = 0; | |||||
| this.eventsToFire = 0; | |||||
| this.lastEndOfDayTime = null; | |||||
| this.events = []; | |||||
| conn = this.connect(eve.system.transports.getAll()); | |||||
| // connect to websocket if not online only | |||||
| if (conn[0].connect !== undefined) { | |||||
| conn[0].connect(EVENTS_AGENT_ADDRESS) | |||||
| .then(function () { | |||||
| console.log('Connected to ', EVENTS_AGENT_ADDRESS); | |||||
| me.rpc.request(EVENTS_AGENT_ADDRESS, { | |||||
| method: "loadEvents", | |||||
| params: {filename: "events.csv", actuallySend: true} | |||||
| }).done(function (reply) { | |||||
| me.amountOfEvents = reply; | |||||
| me.getEvents(AMOUNT_OF_INITIAL_EVENTS); | |||||
| }); | |||||
| }) | |||||
| .catch(function (err) { | |||||
| console.log('Error: Failed to connect to the conductor agent'); | |||||
| console.log(err); | |||||
| // keep trying until the conductor agent is online | |||||
| setTimeout(connect, RECONNECT_DELAY); | |||||
| }); | |||||
| } | |||||
| //use local connection | |||||
| else { | |||||
| EVENTS_AGENT_ADDRESS = 'eventGenerator'; | |||||
| setTimeout(function() { | |||||
| me.rpc.request(EVENTS_AGENT_ADDRESS, { | |||||
| method: "loadEvents", | |||||
| params: {filename: "events.csv", actuallySend: true} | |||||
| }).done(function (reply) { | |||||
| me.amountOfEvents = reply; | |||||
| me.getEvents(AMOUNT_OF_INITIAL_EVENTS); | |||||
| }); | |||||
| },40); | |||||
| } | |||||
| } | |||||
| // extend the eve.Agent prototype | |||||
| AgentGenerator.prototype = Object.create(eve.Agent.prototype); | |||||
| AgentGenerator.prototype.constructor = AgentGenerator; | |||||
| // define RPC functions, preferably in a separated object to clearly distinct | |||||
| // exposed functions from local functions. | |||||
| AgentGenerator.prototype.rpcFunctions = {}; | |||||
| AgentGenerator.prototype.rpcFunctions.receiveEvent = function(params) { | |||||
| // setup timeline | |||||
| this.events.push(JSON.stringify(params)); | |||||
| if (params.performedBy == "global") { | |||||
| this.imposeWorkingHours(params); | |||||
| } | |||||
| else { | |||||
| if (agentList[params.performedBy] === undefined) { | |||||
| agentList[params.performedBy] = new GenericAgent(params.performedBy, params.type); | |||||
| } | |||||
| this.rpc.request(params.performedBy, {method: "newEvent", params: params}); | |||||
| } | |||||
| // check if we need to get another event, its done here to avoid raceconditions | |||||
| if (this.eventsToFire != 0) { | |||||
| var me = this; | |||||
| setTimeout(function() { | |||||
| me.eventNumber += 1; | |||||
| eventCounter.innerHTML = me.eventNumber +""; // make string so it works | |||||
| me.rpc.request(EVENTS_AGENT_ADDRESS, {method:'nextEvent', params:{}}).done(); | |||||
| me.eventsToFire -= 1; | |||||
| },EVENT_DELAY); | |||||
| if (INCREASE_SPEED == true) { | |||||
| EVENT_DELAY = Math.max(0, 1000 - (1000 * (me.eventNumber / 205))); | |||||
| } | |||||
| } | |||||
| }; | |||||
| AgentGenerator.prototype.getEvents = function (count) { | |||||
| if (this.eventNumber + count > this.amountOfEvents) { | |||||
| count = this.amountOfEvents - this.eventNumber; | |||||
| } | |||||
| if (count != 0) { | |||||
| this.eventsToFire = count - 1; | |||||
| this.rpc.request(EVENTS_AGENT_ADDRESS, {method: 'nextEvent', params: {}}).done(); | |||||
| this.eventNumber += 1; | |||||
| eventCounter.innerHTML = this.eventNumber + ""; // make string so it works | |||||
| } | |||||
| }; | |||||
| AgentGenerator.prototype.rpcFunctions.updateOpenJobs = function(params) { | |||||
| var skipJob = params.jobId; | |||||
| var time = params.time; | |||||
| this.moveTimeline(params); | |||||
| for (var agentId in agentList) { | |||||
| if (agentList.hasOwnProperty(agentId)) { | |||||
| agentList[agentId].jobs.updateJobs(time, skipJob); | |||||
| } | |||||
| } | |||||
| }; | |||||
| AgentGenerator.prototype.moveTimeline = function(params) { | |||||
| timeline.setCustomTime(params.time); | |||||
| var range = timeline.getWindow(); | |||||
| var duration = range.end - range.start; | |||||
| var hiddenDates = timeline.body.hiddenDates; | |||||
| var DateUtil = vis.timeline.DateUtil; | |||||
| var hiddenDuration = DateUtil.getHiddenDurationBetween(hiddenDates, range.start, range.end); | |||||
| var visibleDuration = duration - hiddenDuration; | |||||
| var fraction = 0.15; | |||||
| var requiredStartDuration = (1-fraction) * visibleDuration; | |||||
| var requiredEndDuration = fraction * visibleDuration; | |||||
| var convertedTime = new Date(params.time).getTime(); | |||||
| var newStart; | |||||
| var newEnd; | |||||
| var elapsedDuration = 0; | |||||
| var previousPoint = convertedTime; | |||||
| for (var i = hiddenDates.length-1; i > 0; i--) { | |||||
| var startDate = hiddenDates[i].start; | |||||
| var endDate = hiddenDates[i].end; | |||||
| // if time after the cutout, and the | |||||
| if (endDate <= convertedTime) { | |||||
| elapsedDuration += previousPoint - endDate; | |||||
| previousPoint = startDate; | |||||
| if (elapsedDuration >= requiredStartDuration) { | |||||
| newStart = endDate + (elapsedDuration - requiredStartDuration); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (newStart === undefined) { | |||||
| newStart = endDate - (requiredStartDuration - elapsedDuration); | |||||
| } | |||||
| elapsedDuration = 0; | |||||
| previousPoint = convertedTime; | |||||
| for (var i = 0; i < hiddenDates.length; i++) { | |||||
| var startDate = hiddenDates[i].start; | |||||
| var endDate = hiddenDates[i].end; | |||||
| // if time after the cutout, and the | |||||
| if (startDate >= convertedTime) { | |||||
| elapsedDuration += startDate - previousPoint; | |||||
| previousPoint = endDate; | |||||
| if (elapsedDuration >= requiredEndDuration) { | |||||
| newEnd = startDate - (elapsedDuration - requiredEndDuration); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (newEnd === undefined) { | |||||
| newEnd = endDate + (requiredEndDuration - elapsedDuration); | |||||
| } | |||||
| timeline.setWindow(newStart, newEnd, {animate:Math.min(100,EVENT_DELAY)}); | |||||
| }; | |||||
| AgentGenerator.prototype.imposeWorkingHours = function(params) { | |||||
| var time = params.time; | |||||
| var operation = params.operation; | |||||
| for (var agentId in agentList) { | |||||
| if (agentList.hasOwnProperty(agentId)) { | |||||
| var agent = agentList[agentId]; | |||||
| for (var jobId in agent.jobs.openJobs) { | |||||
| if (agent.jobs.openJobs.hasOwnProperty(jobId)) { | |||||
| var job = agent.jobs.openJobs[jobId]; | |||||
| agent.updateAssignment(jobId, job.type, time, operation); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| if (operation == 'endOfDay') { | |||||
| this.lastEndOfDayTime = time; | |||||
| } | |||||
| else { | |||||
| if (this.lastEndOfDayTime !== null) { | |||||
| timelineItems.update({ | |||||
| id: 'night' + uuid(), | |||||
| start: this.lastEndOfDayTime, | |||||
| end: time, | |||||
| type: 'background', | |||||
| className: 'night' | |||||
| }); | |||||
| this.lastEndOfDayTime = null; | |||||
| } | |||||
| } | |||||
| }; | |||||
| AgentGenerator.prototype.printEvents = function() { | |||||
| var str = ""; | |||||
| str += "["; | |||||
| for (var i = 0; i < this.events.length; i++) { | |||||
| str += this.events[i]; | |||||
| if (i < this.events.length - 1) { | |||||
| str += "," | |||||
| } | |||||
| } | |||||
| str += "]"; | |||||
| console.log(str); | |||||
| } | |||||
| @ -0,0 +1,40 @@ | |||||
| /** | |||||
| * Created by Alex on 9/25/2014. | |||||
| */ | |||||
| function DurationData() { | |||||
| this.fields = ['duration','durationWithPause','durationWithStartup','durationWithBoth']; | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| this[this.fields[i]] = 0; | |||||
| } | |||||
| } | |||||
| DurationData.prototype.setData = function(otherData) { | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| this[this.fields[i]] = otherData[this.fields[i]]; | |||||
| } | |||||
| }; | |||||
| DurationData.prototype.getData = function() { | |||||
| var dataObj = {}; | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| dataObj[this.fields[i]] = this[this.fields[i]]; | |||||
| } | |||||
| return dataObj; | |||||
| }; | |||||
| DurationData.prototype.useHighest = function(otherData) { | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| var field = this.fields[i]; | |||||
| if (this[field] < otherData[field]) { | |||||
| this[field] = otherData[field]; | |||||
| } | |||||
| } | |||||
| }; | |||||
| DurationData.prototype.calculateDuration = function(time, timeStart, elapsedTime, elapsedTimeWithPause, startupTime) { | |||||
| this.duration = elapsedTime; | |||||
| this.durationWithPause = elapsedTimeWithPause; | |||||
| this.durationWithStartup = elapsedTime + startupTime.durationWithStartup; | |||||
| this.durationWithBoth = elapsedTimeWithPause + startupTime.durationWithBoth; | |||||
| }; | |||||
| @ -0,0 +1,142 @@ | |||||
| /** | |||||
| * Created by Alex on 9/25/2014. | |||||
| */ | |||||
| function DurationStats() { | |||||
| this.fields = ['duration','durationWithPause','durationWithStartup','durationWithBoth']; | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| this[this.fields[i]] = {mean: 0, std: 0}; | |||||
| } | |||||
| } | |||||
| DurationStats.prototype.clearStats = function() { | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| var field = this.fields[i]; | |||||
| this[field].mean = 0; | |||||
| this[field].std = 0; | |||||
| } | |||||
| }; | |||||
| DurationStats.prototype.sumStats = function(otherData) { | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| var field = this.fields[i]; | |||||
| this[field].mean += otherData[field].mean; | |||||
| this[field].std += Math.pow(otherData[field].std,2); | |||||
| } | |||||
| }; | |||||
| DurationStats.prototype.averageStats = function(datapoints) { | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| var field = this.fields[i]; | |||||
| this[field].mean /= datapoints; | |||||
| this[field].std = Math.sqrt(this[field].std / datapoints); | |||||
| } | |||||
| }; | |||||
| DurationStats.prototype.getMeanData = function() { | |||||
| var dataObj = {}; | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| dataObj[this.fields[i]] = this[this.fields[i]].mean; | |||||
| } | |||||
| return dataObj; | |||||
| }; | |||||
| DurationStats.prototype.getData = function() { | |||||
| var dataObj = {}; | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| dataObj[this.fields[i]] = {}; | |||||
| dataObj[this.fields[i]].mean = this[this.fields[i]].mean; | |||||
| dataObj[this.fields[i]].std = this[this.fields[i]].std; | |||||
| } | |||||
| return dataObj; | |||||
| }; | |||||
| DurationStats.prototype.setData = function(otherData) { | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| var field = this.fields[i]; | |||||
| this[field].mean = otherData[field].mean; | |||||
| this[field].std = otherData[field].std; | |||||
| } | |||||
| }; | |||||
| DurationStats.prototype.generateData = function(otherData) { | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| var field = this.fields[i]; | |||||
| this[field].mean = otherData[i] * 3600000; | |||||
| this[field].std = otherData[i] * 0.1; | |||||
| } | |||||
| }; | |||||
| DurationStats.prototype.useHighest = function(otherData) { | |||||
| for (var i = 0; i < this.fields.length; i++) { | |||||
| var field = this.fields[i]; | |||||
| if (this[field].mean < otherData[field].mean) { | |||||
| this[field].mean = otherData[field].mean; | |||||
| this[field].std = otherData[field].std; | |||||
| } | |||||
| } | |||||
| }; | |||||
| DurationStats.prototype.getFakeStats = function(type) { | |||||
| switch (type) { | |||||
| case "Assemble Coffeemaker": | |||||
| this.generateData([1.3,1.3,1.3,1.3]); | |||||
| break; | |||||
| case "Discuss potential NC": | |||||
| this.generateData([0.5,0.5,0.9,0.9]); | |||||
| break; | |||||
| case "Drilling rework": | |||||
| this.generateData([5,5,8,8]); | |||||
| break; | |||||
| case "Go to station": | |||||
| var a = 0.3; | |||||
| this.generateData([a,a,a,a]); | |||||
| break; | |||||
| case "Inspect finished Coffeemaker": | |||||
| var a = 2; | |||||
| this.generateData([a,a,a,a]); | |||||
| break; | |||||
| case "Inspect potential NC": | |||||
| var a = 0.5; | |||||
| this.generateData([a,a,a,a]); | |||||
| break; | |||||
| case "Kitting Coffeemaker": | |||||
| var a = 1.2; | |||||
| this.generateData([a,a,a,a]); | |||||
| break; | |||||
| case "NC Meeting": | |||||
| var a = 15; | |||||
| this.generateData([3,3.5,a,a]); | |||||
| break; | |||||
| case "Go to NC meeting": | |||||
| var a = 12; | |||||
| this.generateData([a,a,a,a]); | |||||
| break; | |||||
| case "Organise drilling rework": | |||||
| var a = 2; | |||||
| this.generateData([a,a,3,3]); | |||||
| break; | |||||
| case "Produce Coffeemaker": | |||||
| var a = 35; | |||||
| this.generateData([a,a,a,a]); | |||||
| break; | |||||
| case "Transport to delivery": | |||||
| var a = 0.4; | |||||
| this.generateData([a,a,a,a]); | |||||
| break; | |||||
| default: | |||||
| console.log("CANNOT MATCH", type); | |||||
| break; | |||||
| } | |||||
| }; | |||||
| @ -0,0 +1,85 @@ | |||||
| 'use strict'; | |||||
| if (typeof window === 'undefined') { | |||||
| var eve = require('evejs'); | |||||
| } | |||||
| function GenericAgent(id, type) { | |||||
| // execute super constructor | |||||
| eve.Agent.call(this, id); | |||||
| this.id = id; | |||||
| this.rpc = this.loadModule('rpc', this.rpcFunctions); | |||||
| this.connect(eve.system.transports.getAll()); | |||||
| this.type = type; | |||||
| this.jobs = new JobManager(this); | |||||
| this.timelineDataset = timelineItems; | |||||
| timelineGroups.add({id:id, content:type + ": " + id, className: 'timelineGroup ' + type}); | |||||
| this.availableSubgroups = [0,1,2,3,4,5,6,7,8,9,10]; | |||||
| this.freeSubgroups = {}; | |||||
| for (var i = 0; i < this.availableSubgroups.length; i++) { | |||||
| this.freeSubgroups[this.availableSubgroups[i]] = true; | |||||
| } | |||||
| this.usedSubgroups = {}; | |||||
| } | |||||
| // extend the eve.Agent prototype | |||||
| GenericAgent.prototype = Object.create(eve.Agent.prototype); | |||||
| GenericAgent.prototype.constructor = GenericAgent; | |||||
| // define RPC functions, preferably in a separated object to clearly distinct | |||||
| // exposed functions from local functions. | |||||
| GenericAgent.prototype.rpcFunctions = {}; | |||||
| GenericAgent.prototype.allocateSubgroup = function(type) { | |||||
| for (var i = 0; i < this.availableSubgroups.length; i++) { | |||||
| if (this.freeSubgroups[this.availableSubgroups[i]] == true) { | |||||
| this.usedSubgroups[type] = i; | |||||
| this.freeSubgroups[this.availableSubgroups[i]] = false; | |||||
| break; | |||||
| } | |||||
| } | |||||
| }; | |||||
| GenericAgent.prototype.freeSubgroup = function(type) { | |||||
| this.freeSubgroups[this.usedSubgroups[type]] = true; | |||||
| delete this.usedSubgroups[type]; | |||||
| }; | |||||
| GenericAgent.prototype.newAssignment = function(id, type, time, prerequisites) { | |||||
| this.allocateSubgroup(type); | |||||
| this.jobs.add(id, type, time, prerequisites); | |||||
| }; | |||||
| /** | |||||
| * @param id | |||||
| * @param time | |||||
| * @param type | |||||
| */ | |||||
| GenericAgent.prototype.finishAssignment = function(id, type, time) { | |||||
| this.jobs.finish(id, type, time); | |||||
| }; | |||||
| GenericAgent.prototype.updateAssignment = function(id, type, time, operation) { | |||||
| this.jobs.update(id, type, time, operation); | |||||
| }; | |||||
| GenericAgent.prototype.rpcFunctions.newEvent = function(params) { | |||||
| // handle events | |||||
| if (params.operation == 'start') { | |||||
| this.newAssignment(params.jobId, params.assignment, params.time, params.prerequisites) | |||||
| } | |||||
| else if (params.operation == 'finish') { | |||||
| this.finishAssignment(params.jobId, params.assignment, params.time); | |||||
| } | |||||
| else if (params.operation == 'pause' || params.operation == 'resume') { | |||||
| this.updateAssignment(params.jobId,params.assignment,params.time, params.operation); | |||||
| } | |||||
| }; | |||||
| if (typeof window === 'undefined') { | |||||
| module.exports = GenericAgent; | |||||
| } | |||||
| @ -0,0 +1,116 @@ | |||||
| 'use strict'; | |||||
| function uuid() { | |||||
| return (Math.random()*1e15).toString(32) + "-" + (Math.random()*1e15).toString(32); | |||||
| } | |||||
| /** | |||||
| * This is a local assignment, this keeps track on how long an assignment takes THIS worker. | |||||
| * | |||||
| * @param id | |||||
| * @param type | |||||
| * @param timeStart | |||||
| * @constructor | |||||
| */ | |||||
| function Job(id, type, timeStart, agentId, prerequisites) { | |||||
| this.id = id; | |||||
| this.type = type; | |||||
| this.agentId = agentId; | |||||
| this.timeStart = timeStart; | |||||
| this.timeResumed = timeStart; | |||||
| this.timePaused = 0; | |||||
| this.elapsedTime = 0; | |||||
| this.elapsedTimeWithPause = 0; | |||||
| this.endOfDayPause = false; | |||||
| this.paused = false; | |||||
| this.finished = false; | |||||
| this.duration = new DurationData(); | |||||
| this.prediction = new DurationStats(); | |||||
| this.startupTime = new DurationData(); | |||||
| this.predictedStartupTime = new DurationStats(); | |||||
| this.prerequisites = prerequisites; | |||||
| } | |||||
| Job.prototype.prerequisiteFinished = function(params) { | |||||
| var uuid = params.uuid; | |||||
| for (var i = 0; i < this.prerequisites.length; i++) { | |||||
| var prereq = this.prerequisites[i]; | |||||
| if (prereq.uuid == uuid) { | |||||
| prereq.times.setData(params.duration); | |||||
| break; | |||||
| } | |||||
| } | |||||
| }; | |||||
| Job.prototype.watchingPrerequisite = function(preliminaryStats, uuid) { | |||||
| for (var i = 0; i < this.prerequisites.length; i++) { | |||||
| var prereq = this.prerequisites[i]; | |||||
| if (prereq.uuid == uuid) { | |||||
| prereq.stats.setData(preliminaryStats); | |||||
| this.predictedStartupTime.useHighest(preliminaryStats); | |||||
| break; | |||||
| } | |||||
| } | |||||
| }; | |||||
| Job.prototype.finalizePrerequisites = function() { | |||||
| for (var i = 0; i < this.prerequisites.length; i++) { | |||||
| this.startupTime.useHighest(this.prerequisites[i].times); | |||||
| } | |||||
| }; | |||||
| Job.prototype.finish = function(time) { | |||||
| this.finished = true; | |||||
| this.elapsedTime += new Date(time).getTime() - new Date(this.timeResumed).getTime(); | |||||
| this.elapsedTimeWithPause += new Date(time).getTime() - new Date(this.timeResumed).getTime(); | |||||
| this.finalizePrerequisites(); | |||||
| this.duration.calculateDuration(time, this.timeStart, this.elapsedTime, this.elapsedTimeWithPause, this.startupTime); | |||||
| }; | |||||
| Job.prototype.pause = function(time, endOfDay) { | |||||
| // if this is the endOfDay AND the job is paused, count the pause time and set the endOfDay pause to true | |||||
| if (endOfDay == true && this.paused == true) { | |||||
| this.elapsedTimeWithPause += new Date(time).getTime() - new Date(this.timePaused).getTime(); | |||||
| this.endOfDayPause = true; | |||||
| } | |||||
| // if this is the endOfDay AND the job is NOT paused, pause the job, increment the timers | |||||
| else if (endOfDay == true && this.paused == false) { | |||||
| this.elapsedTimeWithPause += new Date(time).getTime() - new Date(this.timeResumed).getTime(); | |||||
| this.elapsedTime += new Date(time).getTime() - new Date(this.timeResumed).getTime(); | |||||
| this.endOfDayPause = true; | |||||
| } | |||||
| else if (this.paused == false) { | |||||
| this.elapsedTimeWithPause += new Date(time).getTime() - new Date(this.timeResumed).getTime(); | |||||
| this.elapsedTime += new Date(time).getTime() - new Date(this.timeResumed).getTime(); | |||||
| this.timePaused = time; | |||||
| this.paused = true; | |||||
| } | |||||
| }; | |||||
| Job.prototype.resume = function(time, startOfDay) { | |||||
| // if the job was paused because of the endOfDay, resume it and set the timeResumed to now | |||||
| if (this.endOfDayPause == true && startOfDay == true && this.paused == false) { | |||||
| this.timeResumed = time; | |||||
| this.endOfDayPause = false; | |||||
| } | |||||
| // if the job was paused before the endOfDay, keep it paused, but set the paused time to now. | |||||
| else if (this.endOfDayPause == true && startOfDay == true && this.paused == true) { | |||||
| this.timePaused = time; | |||||
| this.endOfDayPause = false; | |||||
| } | |||||
| // if this is NOT the start of day and the job was paused, resume job, increment | |||||
| else if (startOfDay == false && this.paused == true) { | |||||
| this.elapsedTimeWithPause += new Date(time).getTime() - new Date(this.timePaused).getTime(); | |||||
| this.timeResumed = time; | |||||
| this.paused = false; | |||||
| } | |||||
| }; | |||||
| @ -0,0 +1,434 @@ | |||||
| 'use strict'; | |||||
| function JobAgent(id) { | |||||
| // execute super constructor | |||||
| eve.Agent.call(this, id); | |||||
| this.rpc = this.loadModule('rpc', this.rpcFunctions); | |||||
| this.connect(eve.system.transports.getAll()); | |||||
| this.id = id; | |||||
| this.type = this.id.replace('job_',''); | |||||
| this.globalStats = new DurationStats(); | |||||
| this.globalStats.getFakeStats(id); | |||||
| this.agentStats = {}; | |||||
| this.allJobs = {}; // used to quickly look up a job ID | |||||
| this.openJobs = {}; // used to check if there is a watcher to | |||||
| this.closedJobs = {};// keeps a list of agentIds containing jobs, used for stats collection | |||||
| // optimization would be nice with running averages, but N samples are needed, wait for demo data. | |||||
| this.watchers = {}; | |||||
| } | |||||
| // extend the eve.Agent prototype | |||||
| JobAgent.prototype = Object.create(eve.Agent.prototype); | |||||
| JobAgent.prototype.constructor = JobAgent; | |||||
| // define RPC functions, preferably in a separated object to clearly distinct | |||||
| // exposed functions from local functions. | |||||
| JobAgent.prototype.rpcFunctions = {}; | |||||
| JobAgent.prototype.expandPrerequisites = function(prerequisites) { | |||||
| var expanded = []; | |||||
| if (prerequisites !== undefined) { | |||||
| for (var i = 0; i < prerequisites.length; i++) { | |||||
| var prereq = prerequisites[i]; | |||||
| if (typeof prereq == 'string') { | |||||
| expanded.push({ | |||||
| jobId: prereq, | |||||
| uuid: uuid(), | |||||
| times : new DurationData(), | |||||
| stats: new DurationStats() | |||||
| }); | |||||
| } | |||||
| else if (typeof prereq == 'object' && prereq.type !== undefined) { //&& prereq.agentId !== undefined not needed since the same items will be added when only type exists | |||||
| prereq.uuid = uuid(); | |||||
| prereq.times = new DurationData(); | |||||
| prereq.stats = new DurationStats(); | |||||
| expanded.push(prereq); | |||||
| } | |||||
| else { | |||||
| console.log('ERROR: cannot use the prerequisites! Not in array of strings or array of objects with correct fields format.'); | |||||
| throw new Error('ERROR: cannot use the prerequisites! Not in array of strings or array of objects with correct fields format.'); | |||||
| } | |||||
| } | |||||
| } | |||||
| return expanded; | |||||
| }; | |||||
| /** | |||||
| * Create new Job for agent | |||||
| * @param params | |||||
| */ | |||||
| JobAgent.prototype.rpcFunctions.add = function(params) { | |||||
| var agentId = params.agentId; | |||||
| var jobId = params.jobId; | |||||
| // create stats if not yet exits | |||||
| if (this.agentStats[agentId] === undefined) { | |||||
| this.agentStats[agentId] = new DurationStats(); | |||||
| } | |||||
| // create open job | |||||
| if (this.openJobs[agentId] === undefined) { | |||||
| this.openJobs[agentId] = {}; | |||||
| } | |||||
| if (this.openJobs[agentId][jobId] !== undefined) { | |||||
| console.log('cannot start new job, jobId:', jobId, ' already exists!'); | |||||
| throw new Error('cannot start new job, jobId:' + jobId + ' already exists!'); | |||||
| } | |||||
| var prerequisites = this.expandPrerequisites(params.prerequisites); | |||||
| this.openJobs[agentId][jobId] = new Job(jobId, this.id, params.time, agentId, prerequisites); | |||||
| this.allJobs[jobId] = this.openJobs[agentId][jobId]; | |||||
| this.addWatchers(jobId, prerequisites); | |||||
| // return prediction | |||||
| var statsData; | |||||
| if (this.agentStats[agentId].duration.mean == 0) { | |||||
| statsData = this.globalStats.getData(); | |||||
| } | |||||
| else { | |||||
| statsData = this.agentStats[agentId].getData(); | |||||
| } | |||||
| return statsData; | |||||
| }; | |||||
| /** | |||||
| * finish the job of an agent | |||||
| * @param params | |||||
| */ | |||||
| JobAgent.prototype.rpcFunctions.finish = function(params) { | |||||
| var agentId = params.agentId; | |||||
| var jobId = params.jobId; | |||||
| var job = this.openJobs[agentId][jobId]; | |||||
| // finish job | |||||
| job.finish(params.time); | |||||
| // notify watchers that a job is finished. | |||||
| if (this.watchers[jobId] !== undefined) { | |||||
| for (var i = 0; i < this.watchers[jobId].length; i++) { | |||||
| var val = this.watchers[jobId][i]; | |||||
| var address = val.address; | |||||
| var parentJobId = val.parentJobId; | |||||
| var uuid = val.uuid; | |||||
| this.rpc.request(address, {method:'watchedJobFinished', params:{ | |||||
| uuid: uuid, | |||||
| parentJobId: parentJobId, | |||||
| duration: job.duration.getData() | |||||
| }}).done(); | |||||
| } | |||||
| } | |||||
| // cleanup watchers | |||||
| delete this.watchers[jobId]; | |||||
| // move from open to closed jobs. | |||||
| if (this.closedJobs[agentId] === undefined) { | |||||
| this.closedJobs[agentId] = {}; | |||||
| } | |||||
| if (this.closedJobs[agentId][jobId] !== undefined) { | |||||
| console.log('cannot close job, jobId:', jobId, ' already exists!'); | |||||
| throw new Error('cannot close job, jobId:' + jobId + ' already exists!'); | |||||
| } | |||||
| this.closedJobs[agentId][jobId] = this.openJobs[agentId][jobId]; | |||||
| delete this.openJobs[agentId][jobId]; | |||||
| this.updateStats(); | |||||
| return { | |||||
| elapsedTime: this.closedJobs[agentId][jobId].elapsedTime, | |||||
| elapsedTimeWithPause: this.closedJobs[agentId][jobId].elapsedTimeWithPause, | |||||
| duration: this.closedJobs[agentId][jobId].duration.getData(), | |||||
| prediction: this.globalStats.getData() | |||||
| }; | |||||
| }; | |||||
| /** | |||||
| * update the job of an agent | |||||
| * @param params | |||||
| */ | |||||
| JobAgent.prototype.rpcFunctions.update = function(params) { | |||||
| var agentId = params.agentId; | |||||
| var jobId = params.jobId; | |||||
| var job = this.openJobs[agentId][jobId]; | |||||
| var operation = params.operation; | |||||
| switch (operation) { | |||||
| case 'pause': | |||||
| job.pause(params.time, false); | |||||
| break; | |||||
| case 'endOfDay': | |||||
| job.pause(params.time, true); | |||||
| break; | |||||
| case 'startOfDay': | |||||
| job.resume(params.time, true); | |||||
| break; | |||||
| case 'resume': | |||||
| job.resume(params.time, false); | |||||
| break; | |||||
| } | |||||
| return {jobId: jobId, type: this.type, elapsedTime: job.elapsedTime, elapsedTimeWithPause: job.elapsedTimeWithPause}; | |||||
| }; | |||||
| /** | |||||
| * return agent stats | |||||
| * @param params | |||||
| * @returns {*} | |||||
| */ | |||||
| JobAgent.prototype.rpcFunctions.getAgentStats = function(params) { | |||||
| return this.agentStats[params.agentId]; | |||||
| }; | |||||
| /** | |||||
| * return global stats | |||||
| * @param params | |||||
| * @returns {{mean: number, std: number}|*} | |||||
| */ | |||||
| JobAgent.prototype.rpcFunctions.getGlobalStats = function(params) { | |||||
| return this.globalStats; | |||||
| }; | |||||
| JobAgent.prototype.rpcFunctions.watchedJobFinished = function(params) { | |||||
| var jobId = params.parentJobId; | |||||
| this.allJobs[jobId].prerequisiteFinished(params); | |||||
| }; | |||||
| /** | |||||
| * | |||||
| * @param params | |||||
| * @param sender | |||||
| * @returns {*} | |||||
| */ | |||||
| JobAgent.prototype.rpcFunctions.addWatcherOnJobId = function(params, sender) { | |||||
| var jobId = params.jobId; | |||||
| var uuid = params.uuid; | |||||
| var parentJobId = params.parentJobId; | |||||
| var job = this.allJobs[jobId]; | |||||
| // if the job is already finished, call the finished callback | |||||
| if (job.finished == true) { | |||||
| this.rpc.request(sender, {method:'watchedJobFinished', params:{ | |||||
| uuid: uuid, | |||||
| parentJobId: parentJobId, | |||||
| duration: job.duration.getData() // we need the pure json data, not the class | |||||
| }}).done(); | |||||
| } | |||||
| else { | |||||
| // we will create a watcher on a job which will alert the watcher when the job is finished with all the times. | |||||
| if (this.watchers[jobId] === undefined) { | |||||
| this.watchers[jobId] = []; | |||||
| } | |||||
| this.watchers[jobId].push({address: params.address, uuid: uuid}); | |||||
| } | |||||
| // return the best prediction we have | |||||
| if (this.agentStats[job.agentId].mean == 0) { | |||||
| return this.globalStats.getData(); // we need the pure json data, not the class | |||||
| } | |||||
| return this.agentStats[job.agentId].getData(); // we need the pure json data, not the class | |||||
| }; | |||||
| /** | |||||
| * | |||||
| * @param params | |||||
| * @param sender | |||||
| * @returns {*} | |||||
| */ | |||||
| JobAgent.prototype.rpcFunctions.addWatcherByAgentID = function(params, sender) { | |||||
| var agentId = params.agentId; | |||||
| var parentJobId = params.parentJobId; | |||||
| var jobId = null; | |||||
| var uuid = params.uuid; | |||||
| var returnStats; | |||||
| // see which statistics collection we will need to return. | |||||
| if (this.agentStats[agentId].duration.mean == 0) { | |||||
| returnStats = this.globalStats; | |||||
| } | |||||
| else { | |||||
| returnStats = this.agentStats[agentId]; | |||||
| } | |||||
| // see if we have an open job with that agent of this type | |||||
| if (this.openJobs[agentId] !== undefined) { | |||||
| for (var jId in this.openJobs[agentId]) { | |||||
| if (this.openJobs[agentId].hasOwnProperty(jId)) { | |||||
| jobId = jId; | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| // there is no open job from supplied agent of this type. return the mean of the return stats | |||||
| if (jobId === null) { | |||||
| this.rpc.request(params.address, {method:'watchedJobFinished', params:{ | |||||
| uuid: uuid, | |||||
| parentJobId: parentJobId, | |||||
| duration: returnStats.getMeanData(), // we need the pure json data, not the class | |||||
| oldData: true | |||||
| }}).done(); | |||||
| } | |||||
| else { | |||||
| params.jobId = jobId; | |||||
| this.rpcFunctions.addWatcherOnJobId.call(this, params, sender); | |||||
| } | |||||
| // return the best prediction we have | |||||
| return returnStats.getData(); // we need the pure json data, not the class | |||||
| }; | |||||
| /** | |||||
| * | |||||
| * @param params | |||||
| * @param sender | |||||
| * @returns {*} | |||||
| */ | |||||
| JobAgent.prototype.rpcFunctions.addWatcherByType = function(params, sender) { | |||||
| // since we cannot watch a global type, we return the global stats at that point. | |||||
| this.rpc.request(params.address, {method:'watchedJobFinished', params:{ | |||||
| uuid: params.uuid, | |||||
| parentJobId: params.parentJobId, | |||||
| duration: this.globalStats.getMeanData(), // we need the pure json data, not the class | |||||
| oldData: true | |||||
| }}).done(); | |||||
| return this.globalStats.getData(); // we need the pure json data, not the class | |||||
| }; | |||||
| /** | |||||
| * | |||||
| * @param parentJobId | ID from the job that wants to WATCH other jobs | |||||
| * @param prerequisites | |||||
| */ | |||||
| JobAgent.prototype.addWatchers = function(parentJobId, prerequisites) { | |||||
| for (var i = 0; i < prerequisites.length; i++) { | |||||
| var prereq = prerequisites[i]; | |||||
| var params = { | |||||
| uuid: prereq.uuid, | |||||
| address: this.id, // this is the callback address | |||||
| parentJobId: parentJobId// this is the job that wants to watch the other one. | |||||
| }; | |||||
| var me = this; | |||||
| if (prereq.jobId !== undefined) { | |||||
| // we now have a parentJobId to watch | |||||
| // we first need to find the type of job this belongs to. | |||||
| this.rpc.request('JobAgentGenerator', {method: 'returnJobAddress', params: {jobId: prereq.jobId}}) | |||||
| // now that we have an address, set a watcher on the job id | |||||
| .done(function (address) { | |||||
| if (address != 'doesNotExist') { | |||||
| params.jobId = prereq.jobId; // this is the job we want to watch | |||||
| me.rpc.request(address, {method: 'addWatcherOnJobId', params: params}) | |||||
| .done(function (preliminaryStats) { | |||||
| me.allJobs[parentJobId].watchingPrerequisite(preliminaryStats, prereq.uuid); | |||||
| }) | |||||
| } | |||||
| else { | |||||
| console.log('ERROR: watch job does not exist.'); | |||||
| throw new Error('ERROR: watch job does not exist.'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| else if (prereq.agentId !== undefined && prereq.type !== undefined) { | |||||
| // we now have an agentId and a jobType to watch. | |||||
| params.agentId = prereq.agentId; // this is the job we want to watch | |||||
| this.rpc.request(prereq.type, {method: 'addWatcherByAgentID', params: params}) | |||||
| .done(function (preliminaryStats) { | |||||
| me.allJobs[parentJobId].watchingPrerequisite(preliminaryStats, prereq.uuid); | |||||
| }) | |||||
| } | |||||
| else if (prereq.type !== undefined) { | |||||
| this.rpc.request(prereq.type, {method: 'addWatcherByType', params: params}) | |||||
| .done(function (preliminaryStats) { | |||||
| me.allJobs[parentJobId].watchingPrerequisite(preliminaryStats, prereq.uuid); | |||||
| }) | |||||
| } | |||||
| } | |||||
| }; | |||||
| JobAgent.prototype.updatePredictedStartup = function(jobId, prediction) { | |||||
| var jobPrediction = this.allJobs[jobId].predictedStartupTime; | |||||
| jobPrediction.mean = Math.max(jobPrediction.mean, prediction.mean); | |||||
| jobPrediction.std = Math.sqrt(Math.pow(jobPrediction.std,2) + Math.pow(prediction.std,2)); | |||||
| this.allJobs[jobId].prerequisitesCount += 1; | |||||
| }; | |||||
| /** | |||||
| * Update all statistics | |||||
| * | |||||
| */ | |||||
| JobAgent.prototype.updateStats = function() { | |||||
| this.globalStats.clearStats(); | |||||
| var count = 0; | |||||
| for (var agentId in this.closedJobs) { | |||||
| if (this.closedJobs.hasOwnProperty(agentId)) { | |||||
| var collection = this.closedJobs[agentId]; | |||||
| // could be optimised with rolling average for efficient memory management | |||||
| this.agentStats[agentId].setData(this.updateStatsIn(collection)); | |||||
| this.globalStats.sumStats(this.agentStats[agentId]); | |||||
| count += 1; | |||||
| } | |||||
| } | |||||
| this.globalStats.averageStats(count); | |||||
| }; | |||||
| /** | |||||
| * | |||||
| * @param collection | |||||
| * @returns {{duration: *, durationWithPause: *, durationWithStartup: *, durationWithBoth: *}} | |||||
| */ | |||||
| JobAgent.prototype.updateStatsIn = function(collection) { | |||||
| var stats = {}; | |||||
| for (var i = 0; i < this.globalStats.fields.length; i++) { | |||||
| var field = this.globalStats.fields[i]; | |||||
| stats[field] = this.collectStatsIn(collection, field); | |||||
| } | |||||
| return stats; | |||||
| }; | |||||
| JobAgent.prototype.collectStatsIn = function(collection, field) { | |||||
| var total = 0; | |||||
| var mean = 0; | |||||
| var std = 0; | |||||
| var minVal = 1e16; | |||||
| var maxVal = 0; | |||||
| var count = 0; | |||||
| for (var jobId in collection) { | |||||
| if (collection.hasOwnProperty(jobId)) { | |||||
| var value = collection[jobId].duration[field]; | |||||
| maxVal = value > maxVal ? value : maxVal; | |||||
| minVal = value < minVal ? value : minVal; | |||||
| total += collection[jobId].duration[field]; | |||||
| count += 1; | |||||
| } | |||||
| } | |||||
| if (count > 0) { | |||||
| mean = total / count; | |||||
| for (var jobId in collection) { | |||||
| if (collection.hasOwnProperty(jobId)) { | |||||
| std += Math.pow(collection[jobId].duration[field] - mean,2); | |||||
| } | |||||
| } | |||||
| std = Math.sqrt(std/count); | |||||
| return {mean: mean, std: std, min: minVal, max: maxVal}; | |||||
| } | |||||
| else { | |||||
| return {mean: 0, std: 0, min: 0, max: 0}; | |||||
| } | |||||
| }; | |||||
| JobAgent.prototype.hasJob = function(params) { | |||||
| return this.allJobs[params.jobId] !== undefined; | |||||
| }; | |||||
| @ -0,0 +1,169 @@ | |||||
| 'use strict'; | |||||
| function JobAgentGenerator(id) { | |||||
| // execute super constructor | |||||
| eve.Agent.call(this, id); | |||||
| this.rpc = this.loadModule('rpc', this.rpcFunctions); | |||||
| this.connect(eve.system.transports.getAll()); | |||||
| } | |||||
| // extend the eve.Agent prototype | |||||
| JobAgentGenerator.prototype = Object.create(eve.Agent.prototype); | |||||
| JobAgentGenerator.prototype.constructor = AgentGenerator; | |||||
| // define RPC functions, preferably in a separated object to clearly distinct | |||||
| // exposed functions from local functions. | |||||
| JobAgentGenerator.prototype.rpcFunctions = {}; | |||||
| JobAgentGenerator.prototype.rpcFunctions.createJob = function(params) { | |||||
| var jobAgentName = params.type; | |||||
| if (jobList[jobAgentName] === undefined) { | |||||
| jobList[jobAgentName] = new JobAgent(jobAgentName); | |||||
| graph2dGroups.add([ | |||||
| { | |||||
| id: jobAgentName+'_pred_duration_std_lower', | |||||
| content: "prediction", | |||||
| className: 'prediction_std', | |||||
| options: {drawPoints:false} | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithPause_std_lower', | |||||
| content: "predWithPause", | |||||
| className: 'prediction_std', | |||||
| options: {drawPoints:false} | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithStartup_std_lower', | |||||
| content: "predWithStartup", | |||||
| className: 'prediction_std', | |||||
| options: {drawPoints:false} | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithBoth_std_lower', | |||||
| content: "predWithBoth", | |||||
| className: 'prediction_std', | |||||
| options: {drawPoints:false} | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_duration_std_higher', | |||||
| content: "prediction", | |||||
| className: 'prediction_std', | |||||
| options: {drawPoints:false} | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithPause_std_higher', | |||||
| content: "predWithPause", | |||||
| className: 'prediction_std', | |||||
| options: {drawPoints:false} | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithStartup_std_higher', | |||||
| content: "predWithStartup", | |||||
| className: 'prediction_std', | |||||
| options: {drawPoints:false} | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithBoth_std_higher', | |||||
| content: "predWithBoth", | |||||
| className: 'prediction_std', | |||||
| options: {drawPoints:false} | |||||
| },{ | |||||
| id: jobAgentName+'_pred_duration_original', | |||||
| content: "prediction", | |||||
| className: 'prediction_original' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithPause_original', | |||||
| content: "predWithPause", | |||||
| className: 'prediction_original' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithStartup_original', | |||||
| content: "predWithStartup", | |||||
| className: 'prediction_original' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithBoth_original', | |||||
| content: "predWithBoth", | |||||
| className: 'prediction_original' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_duration', | |||||
| content: "prediction", | |||||
| className: 'prediction' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithPause', | |||||
| content: "predWithPause", | |||||
| className: 'prediction' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithStartup', | |||||
| content: "predWithStartup", | |||||
| className: 'prediction' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_pred_durationWithBoth', | |||||
| content: "predWithBoth", | |||||
| className: 'prediction' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_duration', | |||||
| content: "duration", | |||||
| className: 'duration' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_durationWithPause', | |||||
| content: "durationWithPause", | |||||
| className: 'duration' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_durationWithStartup', | |||||
| content: "durationWithStartup", | |||||
| className: 'duration' | |||||
| }, | |||||
| { | |||||
| id: jobAgentName+'_durationWithBoth', | |||||
| content: "durationWithBoth", | |||||
| className: 'duration' | |||||
| } | |||||
| ]); | |||||
| var visibilityUpdate = {}; | |||||
| //visibilityUpdate[jobAgentName+'_pred'] = false; | |||||
| //visibilityUpdate[jobAgentName+'_predWithPause'] = false; | |||||
| //visibilityUpdate[jobAgentName+'_predWithStartup'] = false; | |||||
| //visibilityUpdate[jobAgentName+'_predWithBoth'] = false; | |||||
| //visibilityUpdate[jobAgentName+'_duration'] = false; | |||||
| //visibilityUpdate[jobAgentName+'_durationWithPause'] = false; | |||||
| //visibilityUpdate[jobAgentName+'_durationWithStartup'] = false; | |||||
| //visibilityUpdate[jobAgentName+'_durationWithBoth'] = false; | |||||
| graph2d.setOptions({groups:{visible:visibilityUpdate}}); | |||||
| refreshJobs(); | |||||
| } | |||||
| }; | |||||
| JobAgentGenerator.prototype.rpcFunctions.returnJobAddress = function(params) { | |||||
| var instanceId = params.instanceId; | |||||
| var hasJob = false; | |||||
| for (var jobAgentName in jobList) { | |||||
| if (jobList.hasOwnProperty(jobAgentName)) { | |||||
| hasJob = jobList[jobAgentName].hasJob(instanceId); | |||||
| if (hasJob == true) { | |||||
| return jobAgentName; | |||||
| } | |||||
| } | |||||
| } | |||||
| return "doesNotExist"; | |||||
| }; | |||||
| JobAgentGenerator.prototype.getAllJobNames = function() { | |||||
| var list = []; | |||||
| for (var jobAgentName in jobList) { | |||||
| if (jobList.hasOwnProperty(jobAgentName)) { | |||||
| list.push(jobAgentName); | |||||
| } | |||||
| } | |||||
| return list; | |||||
| }; | |||||
| @ -0,0 +1,428 @@ | |||||
| 'use strict'; | |||||
| function JobManager(agent) { | |||||
| this.agent = agent; | |||||
| this.jobs = { | |||||
| id:{}, | |||||
| type:{ | |||||
| open:{}, | |||||
| closed:{} | |||||
| } | |||||
| }; | |||||
| this.openJobs = {}; | |||||
| } | |||||
| JobManager.prototype.add = function(id, type, time, prerequisites) { | |||||
| var me = this; | |||||
| // create job agent. This agent will keep track of the global job stats. Jobs per type. | |||||
| this.agent.rpc.request('jobAgentGenerator',{method:'createJob', params:{type:type}}).done(); | |||||
| this.jobs.id[id] = { | |||||
| type: type, | |||||
| startTime: time, | |||||
| prediction: null, | |||||
| predictionCounter: 0, | |||||
| elapsedTime: 0, | |||||
| elapsedTimeWithPause: 0, | |||||
| pauseCount: 0, | |||||
| endOfDay: false, | |||||
| paused: false, | |||||
| pauseAreaID: "" | |||||
| }; | |||||
| // assign agent to job. | |||||
| this.agent.rpc.request(type, {method:'add', params:{ | |||||
| agentId: this.agent.id, | |||||
| time:time, | |||||
| jobId: id, | |||||
| prerequisites: prerequisites | |||||
| }}) | |||||
| .done(function (prediction) { | |||||
| if (prediction.duration.mean != 0) { | |||||
| me.agent.timelineDataset.update({ | |||||
| id: id + "_predMean0", | |||||
| start: time, | |||||
| end: new Date(time).getTime() + prediction.duration.mean, | |||||
| group: me.agent.id, | |||||
| type: 'range', | |||||
| content: "", | |||||
| subgroup: me.agent.usedSubgroups[type], | |||||
| className: 'prediction' | |||||
| }); | |||||
| } | |||||
| me.jobs.id[id].prediction = prediction; | |||||
| }); | |||||
| if (this.jobs.type.open[type] === undefined) {this.jobs.type.open[type] = {}} | |||||
| this.jobs.type.open[type][id] = time; | |||||
| this.openJobs[id] = this.jobs.id[id]; | |||||
| var addQuery = [{id:id, start:time, content:"started: "+ type, group:this.agent.id, subgroup: this.agent.usedSubgroups[type]}]; | |||||
| this.agent.timelineDataset.add(addQuery); | |||||
| this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}).done(); | |||||
| }; | |||||
| JobManager.prototype.finish = function(id, type, time) { | |||||
| var me = this; | |||||
| // finish the job. | |||||
| this.agent.rpc.request(type, {method:'finish', params:{ | |||||
| agentId: this.agent.id, | |||||
| time: time, | |||||
| jobId: id | |||||
| }}) | |||||
| .done(function (reply) { | |||||
| var prediction = reply.prediction; | |||||
| var originalPrediction = me.jobs.id[id].prediction; | |||||
| me.jobs.id[id].elapsedTime = reply.elapsedTime; | |||||
| me.jobs.id[id].elapsedTimeWithPause = reply.elapsedTimeWithPause; | |||||
| me.updateDataSetsFinish(id, type, time, me.jobs.id[id].prediction); | |||||
| graph2dDataset.push({x: time, y: reply.duration.duration/3600000 ,group: type + '_duration', type: type}); | |||||
| graph2dDataset.push({x: time, y: reply.duration.durationWithPause/3600000 ,group: type + '_durationWithPause', type: type}); | |||||
| graph2dDataset.push({x: time, y: reply.duration.durationWithStartup/3600000 ,group: type + '_durationWithStartup', type: type}); | |||||
| graph2dDataset.push({x: time, y: reply.duration.durationWithBoth/3600000 ,group: type + '_durationWithBoth', type: type}); | |||||
| graph2dDataset.push({x: time, y: prediction.duration.mean/3600000 ,group: type + '_pred_duration', type: type}); | |||||
| graph2dDataset.push({x: time, y: prediction.durationWithPause.mean/3600000 ,group: type + '_pred_durationWithPause', type: type}); | |||||
| graph2dDataset.push({x: time, y: prediction.durationWithStartup.mean/3600000 ,group: type + '_pred_durationWithStartup', type: type}); | |||||
| graph2dDataset.push({x: time, y: prediction.durationWithBoth.mean/3600000 ,group: type + '_pred_durationWithBoth', type: type}); | |||||
| graph2dDataset.push({x: time, y: (prediction.duration.mean + prediction.duration.std)/3600000 ,group: type + '_pred_duration_std_higher', type: type}); | |||||
| graph2dDataset.push({x: time, y: (prediction.durationWithPause.mean + prediction.durationWithPause.std)/3600000 ,group: type + '_pred_durationWithPause_std_higher', type: type}); | |||||
| graph2dDataset.push({x: time, y: (prediction.durationWithStartup.mean + prediction.durationWithStartup.std)/3600000 ,group: type + '_pred_durationWithStartup_std_higher', type: type}); | |||||
| graph2dDataset.push({x: time, y: (prediction.durationWithBoth.mean + prediction.durationWithBoth.std)/3600000 ,group: type + '_pred_durationWithBoth_std_higher', type: type}); | |||||
| graph2dDataset.push({x: time, y: (prediction.duration.mean - prediction.duration.std)/3600000 ,group: type + '_pred_duration_std_lower', type: type}); | |||||
| graph2dDataset.push({x: time, y: (prediction.durationWithPause.mean - prediction.durationWithPause.std)/3600000 ,group: type + '_pred_durationWithPause_std_lower', type: type}); | |||||
| graph2dDataset.push({x: time, y: (prediction.durationWithStartup.mean - prediction.durationWithStartup.std)/3600000 ,group: type + '_pred_durationWithStartup_std_lower', type: type}); | |||||
| graph2dDataset.push({x: time, y: (prediction.durationWithBoth.mean - prediction.durationWithBoth.std)/3600000 ,group: type + '_pred_durationWithBoth_std_lower', type: type}); | |||||
| graph2dDataset.push({x: time, y: originalPrediction.duration.mean/3600000 ,group: type + '_pred_duration_original', type: type}); | |||||
| graph2dDataset.push({x: time, y: originalPrediction.durationWithPause.mean/3600000 ,group: type + '_pred_durationWithPause_original', type: type}); | |||||
| graph2dDataset.push({x: time, y: originalPrediction.durationWithStartup.mean/3600000 ,group: type + '_pred_durationWithStartup_original', type: type}); | |||||
| graph2dDataset.push({x: time, y: originalPrediction.durationWithBoth.mean/3600000 ,group: type + '_pred_durationWithBoth_original', type: type}); | |||||
| }); | |||||
| delete this.jobs.type.open[type][id]; | |||||
| delete this.openJobs[id]; | |||||
| if (this.jobs.type.closed[type] === undefined) {this.jobs.type.closed[type] = {}} | |||||
| this.jobs.type.closed[type][id] = time; | |||||
| }; | |||||
| JobManager.prototype.update = function(id, type, time, operation) { | |||||
| var me = this; | |||||
| var eventId = uuid(); | |||||
| if (operation == 'endOfDay' || operation == 'startOfDay') { | |||||
| for (var jobId in this.openJobs) { | |||||
| if (this.openJobs.hasOwnProperty(jobId)) { | |||||
| var type = this.openJobs[jobId].type; | |||||
| this.agent.rpc.request(type, {method:'update', params:{ | |||||
| agentId: this.agent.id, | |||||
| time: time, | |||||
| jobId: jobId, | |||||
| operation: operation | |||||
| }}) | |||||
| .done(function (reply) { | |||||
| me.jobs.id[reply.jobId].elapsedTime = reply.elapsedTime; | |||||
| me.jobs.id[reply.jobId].elapsedTimeWithPause = reply.elapsedTimeWithPause; | |||||
| me.updateDataSetsOperation(reply.jobId, reply.type, time, operation, eventId); | |||||
| }); | |||||
| } | |||||
| } | |||||
| } | |||||
| else { | |||||
| this.agent.rpc.request(type, {method:'update', params:{ | |||||
| agentId: this.agent.id, | |||||
| time: time, | |||||
| jobId: id, | |||||
| operation: operation | |||||
| }}) | |||||
| .done(function (reply) { | |||||
| me.jobs.id[id].elapsedTime = reply.elapsedTime; | |||||
| me.jobs.id[id].elapsedTimeWithPause = reply.elapsedTimeWithPause; | |||||
| me.updateDataSetsOperation(id, type, time, operation, eventId); | |||||
| }); | |||||
| } | |||||
| }; | |||||
| JobManager.prototype.updateDataSetsOperation = function(id, type, time, operation, eventId) { | |||||
| switch (operation) { | |||||
| case 'pause': | |||||
| this.updateDataSetsPause(id, type, time, operation, this.jobs.id[id].prediction, eventId); | |||||
| break; | |||||
| case 'endOfDay': | |||||
| this.updateDataSetsPause(id, type, time, operation, this.jobs.id[id].prediction, eventId); | |||||
| break; | |||||
| case 'startOfDay': | |||||
| this.updateDataSetsResume(id, type, time, operation, this.jobs.id[id].prediction, eventId); | |||||
| break; | |||||
| case 'resume': | |||||
| this.updateDataSetsResume(id, type, time, operation, this.jobs.id[id].prediction, eventId); | |||||
| break; | |||||
| } | |||||
| }; | |||||
| JobManager.prototype.updateDataSetsFinish = function(id, type, time, prediction) { | |||||
| var updateQuery = []; | |||||
| var field = 'duration'; | |||||
| var elapsedTime = this.jobs.id[id].elapsedTime; | |||||
| // gather statistic indicator data | |||||
| if (this.jobs.id[id].pauseCount > 0) { | |||||
| field = 'durationWithPause'; | |||||
| elapsedTime = this.jobs.id[id].elapsedTimeWithPause; | |||||
| } | |||||
| // generate indicator | |||||
| var predictedTimeLeft = 0; | |||||
| if (prediction[field].mean != 0) { | |||||
| predictedTimeLeft = prediction[field].mean - elapsedTime; | |||||
| this.updatePredictionOnFinish(id, type, time, prediction[field], elapsedTime); | |||||
| } | |||||
| updateQuery.push({ | |||||
| id: id, | |||||
| end: time, | |||||
| content: type, | |||||
| type: 'range', | |||||
| className: 'finished', | |||||
| style: 'background-color: ' + this.generateColors(predictedTimeLeft, elapsedTime) + ' !important;' | |||||
| }); | |||||
| this.agent.freeSubgroup(type); | |||||
| this.agent.timelineDataset.update(updateQuery); | |||||
| this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}).done(); | |||||
| }; | |||||
| JobManager.prototype.updateDataSetsPause = function(id, type, time, operation, prediction, eventId) { | |||||
| var updateQuery = []; | |||||
| var image = '<img src="./images/control_pause.png" class="icon"/>'; | |||||
| var flagId = id + "_pauseNotifier" + eventId; | |||||
| var title = 'Calling RAO for possible NC'; | |||||
| // this causes there to be only one flag for the end of day as well as a moon icon | |||||
| if (operation == 'endOfDay') { | |||||
| // don't end-of-day jobs twice | |||||
| if (this.jobs.id[id].endOfDay == true) { | |||||
| return; | |||||
| } | |||||
| this.jobs.id[id].endOfDay = true; | |||||
| image = '<img src="./images/moon.png" class="icon"/>'; | |||||
| flagId = id + "endOfDayNotifier" + eventId; | |||||
| title = "End of working day" | |||||
| } | |||||
| else { | |||||
| this.jobs.id[id].pauseCount += 1; | |||||
| this.jobs.id[id].pauseAreaID = this.agent.id + "_" + "_pauseArea_" + eventId; | |||||
| var shadedPauseArea = { | |||||
| id: this.jobs.id[id].pauseAreaID, | |||||
| start: time, | |||||
| end: time, | |||||
| content: '', | |||||
| group: this.agent.id, | |||||
| subgroup: this.agent.usedSubgroups[type], | |||||
| className: 'pausedArea', | |||||
| title: 'Job paused.' | |||||
| }; | |||||
| updateQuery.push(shadedPauseArea); | |||||
| } | |||||
| var field = 'duration'; | |||||
| var elapsedTime = this.jobs.id[id].elapsedTime; | |||||
| if (this.jobs.id[id].pauseCount > 0) { | |||||
| field = 'durationWithPause'; | |||||
| elapsedTime = this.jobs.id[id].elapsedTimeWithPause; | |||||
| } | |||||
| updateQuery.push({id: id, end: time, content: type, type: 'range'}); | |||||
| var imageUpdate = { | |||||
| id: flagId, | |||||
| start: time, | |||||
| end: time, | |||||
| content: image, | |||||
| group: this.agent.id, | |||||
| subgroup: this.agent.usedSubgroups[type], | |||||
| className: 'pause', | |||||
| title: title | |||||
| }; | |||||
| if (this.agent.id == "Paolo") { | |||||
| imageUpdate.title = "Going to inspect possible NC."; | |||||
| } | |||||
| updateQuery.push(imageUpdate); | |||||
| var predictedTimeLeft = prediction[field].mean - elapsedTime; | |||||
| var predictionExists = prediction[field].mean != 0; | |||||
| // update the predicted line if the job is not ALREADY pauseds | |||||
| if (predictedTimeLeft > 0 && predictionExists == true && this.jobs.id[id].paused != true) { | |||||
| updateQuery.push({ | |||||
| id: id + "_predMean" + this.jobs.id[id].predictionCounter, | |||||
| end: time, | |||||
| group: this.agent.id, | |||||
| className: 'prediction' | |||||
| }) | |||||
| } | |||||
| // set the status to paused if needed | |||||
| if (operation != 'endOfDay') { | |||||
| this.jobs.id[id].paused = true; | |||||
| } | |||||
| this.agent.timelineDataset.update(updateQuery); | |||||
| this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}) | |||||
| }; | |||||
| JobManager.prototype.updateDataSetsResume = function(id, type, time, operation, prediction, eventId) { | |||||
| var updateQuery = []; | |||||
| var image = '<img src="./images/control_play.png" class="icon"/>'; | |||||
| var flagId = id + "_resumeNotifier" + eventId; | |||||
| // this causes there to be only one flag for the start of day as well as a sun icon | |||||
| if (operation == 'startOfDay') { | |||||
| // don't start-of-day jobs twice | |||||
| if (this.jobs.id[id].endOfDay == false) { | |||||
| return; | |||||
| } | |||||
| this.jobs.id[id].endOfDay = false; | |||||
| image = '<img src="./images/sun.png" class="icon"/>'; | |||||
| flagId = id + "startOfDayNotifier_" + eventId; | |||||
| } | |||||
| else { | |||||
| updateQuery.push({id: this.jobs.id[id].pauseAreaID, end: time, content: '', type: 'range'}); | |||||
| this.jobs.id[id].pauseAreaID = ""; | |||||
| this.jobs.id[id].pauseCount += 1; | |||||
| } | |||||
| var field = 'duration'; | |||||
| var elapsedTime = this.jobs.id[id].elapsedTime; | |||||
| if (this.jobs.id[id].pauseCount > 0) { | |||||
| field = 'durationWithPause'; | |||||
| elapsedTime = this.jobs.id[id].elapsedTimeWithPause; | |||||
| } | |||||
| updateQuery.push({id: id, end: time, content: type, type: 'range'}); | |||||
| updateQuery.push({ | |||||
| id: flagId, | |||||
| start: time, | |||||
| end: time, | |||||
| content: image, | |||||
| group: this.agent.id, | |||||
| subgroup: this.agent.usedSubgroups[type], | |||||
| className: 'pause', | |||||
| title: 'Resuming job' | |||||
| }); | |||||
| var predictedTimeLeft = prediction[field].mean - elapsedTime; | |||||
| // no not increase the prediction line at the start of the day if the job is PAUSED | |||||
| if (predictedTimeLeft > 0 && !(operation == 'startOfDay' && this.jobs.id[id].paused == true)) { | |||||
| this.jobs.id[id].predictionCounter += 1; | |||||
| updateQuery.push({ | |||||
| id: id + "_predMean" + this.jobs.id[id].predictionCounter, | |||||
| start: time, | |||||
| end: new Date(time).getTime() + predictedTimeLeft, | |||||
| group: this.agent.id, | |||||
| content: "", | |||||
| subgroup: this.agent.usedSubgroups[type], | |||||
| type: 'range', | |||||
| className: 'prediction' | |||||
| }); | |||||
| } | |||||
| // resume if needed | |||||
| if (operation != 'startOfDay'){ | |||||
| this.jobs.id[id].paused = false; | |||||
| } | |||||
| this.agent.timelineDataset.update(updateQuery); | |||||
| this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}) | |||||
| }; | |||||
| JobManager.prototype.updatePredictionOnFinish = function(id,type,time,prediction,elapsedTime) { | |||||
| if (prediction.mean != 0) { | |||||
| var predictedTimeLeft = prediction.mean - elapsedTime; | |||||
| if (predictedTimeLeft > 0) { | |||||
| this.agent.timelineDataset.remove({id: id + "_predMean" + this.jobs.id[id].predictionCounter}) | |||||
| } | |||||
| } | |||||
| }; | |||||
| JobManager.prototype.updateJobs = function(time, skipId) { | |||||
| var updateQuery = []; | |||||
| for (var jobId in this.openJobs) { | |||||
| if (this.openJobs.hasOwnProperty(jobId) && jobId != skipId) { | |||||
| var type = this.openJobs[jobId].type; | |||||
| var prediction = this.openJobs[jobId].prediction; | |||||
| var predictedTimeLeft = 0; | |||||
| // never been paused before | |||||
| if (this.jobs.id[jobId].pauseCount == 0) { | |||||
| if (prediction !== null && prediction.duration !== null) { | |||||
| predictedTimeLeft = prediction.duration.mean - this.jobs.id[jobId].elapsedTime; | |||||
| } | |||||
| } | |||||
| else { | |||||
| if (prediction !== null && prediction.durationWithPause !== null) { | |||||
| predictedTimeLeft = prediction.durationWithPause.mean - this.jobs.id[jobId].elapsedTimeWithPause; | |||||
| } | |||||
| } | |||||
| updateQuery.push({id: jobId, end: time, content: type, type: 'range'}); | |||||
| if (this.openJobs[jobId].paused == true) { | |||||
| updateQuery.push({id: this.openJobs[jobId].pauseAreaID, end: time}); | |||||
| } | |||||
| } | |||||
| } | |||||
| this.agent.timelineDataset.update(updateQuery); | |||||
| }; | |||||
| JobManager.prototype.generateColors = function(predictedTime, elapsedTime) { | |||||
| var ratio = (elapsedTime + predictedTime) / elapsedTime; | |||||
| if (ratio > 1) { | |||||
| ratio = Math.min(2,ratio) - 1; // 2 -- 1 | |||||
| var rgb = HSVToRGB(94/360,ratio*0.6 + 0.2,1); | |||||
| } | |||||
| else { | |||||
| ratio = Math.max(0.5,ratio) - 0.5; // 1 -- 0.5 | |||||
| var rgb = HSVToRGB(40/360,(1-(2*ratio))*0.6 + 0.1 ,1); | |||||
| } | |||||
| return "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; | |||||
| }; | |||||
| function HSVToRGB(h, s, v) { | |||||
| var r, g, b; | |||||
| var i = Math.floor(h * 6); | |||||
| var f = h * 6 - i; | |||||
| var p = v * (1 - s); | |||||
| var q = v * (1 - f * s); | |||||
| var t = v * (1 - (1 - f) * s); | |||||
| switch (i % 6) { | |||||
| case 0: r = v, g = t, b = p; break; | |||||
| case 1: r = q, g = v, b = p; break; | |||||
| case 2: r = p, g = v, b = t; break; | |||||
| case 3: r = p, g = q, b = v; break; | |||||
| case 4: r = t, g = p, b = v; break; | |||||
| case 5: r = v, g = p, b = q; break; | |||||
| } | |||||
| return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) }; | |||||
| } | |||||
| function RGBToHSV (red,green,blue) { | |||||
| red=red/255; green=green/255; blue=blue/255; | |||||
| var minRGB = Math.min(red,Math.min(green,blue)); | |||||
| var maxRGB = Math.max(red,Math.max(green,blue)); | |||||
| // Black-gray-white | |||||
| if (minRGB == maxRGB) { | |||||
| return {h:0,s:0,v:minRGB}; | |||||
| } | |||||
| // Colors other than black-gray-white: | |||||
| var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red); | |||||
| var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5); | |||||
| var hue = 60*(h - d/(maxRGB - minRGB))/360; | |||||
| var saturation = (maxRGB - minRGB)/maxRGB; | |||||
| var value = maxRGB; | |||||
| return {h:hue,s:saturation,v:value}; | |||||
| } | |||||
| @ -0,0 +1,301 @@ | |||||
| body { | |||||
| font-family : Verdana, "Bitstream Vera Sans", "DejaVu Sans", Tahoma, Geneva, Arial, Sans-serif; | |||||
| font-size: 13px; | |||||
| min-width:980px; | |||||
| } | |||||
| div.group { | |||||
| background-color: rgba(0, 0, 0, 0.005); | |||||
| } | |||||
| /*.vis.timeline .item.background {*/ | |||||
| /*background-color: rgba(40, 148, 255, 0.20) !important;*/ | |||||
| /*z-index:9999 !important;*/ | |||||
| /*}*/ | |||||
| .vis.timeline .item.range.prediction { | |||||
| border: 1px solid rgba(0, 164, 255, 0.50) !important; | |||||
| background-color: rgba(28, 183, 255, 0.10) !important; | |||||
| box-shadow: rgba(0, 83, 128, 0.15) 0px 0px 10px !important; | |||||
| z-index:-1 !important; | |||||
| height:34px !important; | |||||
| } | |||||
| .vis.timeline .item.background.night { | |||||
| background-color: rgba(60, 60, 60, 0.3) !important; | |||||
| z-index:9999 !important; | |||||
| } | |||||
| .vis.timeline .labelset .timelineGroup .inner { | |||||
| /*height:100px !important;*/ | |||||
| width:140px !important; | |||||
| } | |||||
| .vis.timeline .labelset .timelineGroup.worker{ | |||||
| background-color: #ffffff; | |||||
| } | |||||
| .vis.timeline .labelset .timelineGroup.rao{ | |||||
| background-color: #f5f5f5; | |||||
| } | |||||
| .vis.timeline .labelset .timelineGroup.pm{ | |||||
| background-color: #dfeaf2; | |||||
| } | |||||
| .vis.timeline .labelset .timelineGroup.mt{ | |||||
| background-color: #b8d6e6; | |||||
| } | |||||
| .vis.timeline .item.range { | |||||
| height:34px !important; | |||||
| } | |||||
| .vis.timeline .item.range.pause{ | |||||
| height:34px !important; | |||||
| width:26px !important; | |||||
| box-shadow: rgba(0,0,0,0.2) 0px 0px 10px !important; | |||||
| padding:3px !important; | |||||
| z-index:999999; | |||||
| background-color: #ffffff !important | |||||
| } | |||||
| .vis.timeline .item.range.pausedArea{ | |||||
| height:34px !important; | |||||
| padding:3px !important; | |||||
| z-index:999950; | |||||
| border-width: 0px !important; | |||||
| background: url('../images/shaded.png'); | |||||
| background-color: rgba(0,0,0,0) !important; | |||||
| } | |||||
| .vis.timeline .item { | |||||
| border-radius: 0px !important; | |||||
| border-color: #7d807d !important; | |||||
| background-color: #ffffff !important; | |||||
| /*background-color: #c4e0ff !important;*/ | |||||
| } | |||||
| .vis.timeline .item.box { | |||||
| height:22px !important; | |||||
| border-radius: 0px !important; | |||||
| border-color: #7d807d !important; | |||||
| background-color: #ffffff !important; | |||||
| box-shadow: rgba(0, 0, 0, 0.52) 0px 0px 12px 0px; | |||||
| } | |||||
| img.icon{ | |||||
| width:16px; | |||||
| height:16px; | |||||
| } | |||||
| #multiselect { | |||||
| position:relative; | |||||
| top:1px; | |||||
| width:200px; | |||||
| height:452px; | |||||
| } | |||||
| table.wrapper { | |||||
| position:relative; | |||||
| height:500px; | |||||
| width:100%; | |||||
| } | |||||
| /*table.wrapper td {*/ | |||||
| /*border:1px solid #ff0000;*/ | |||||
| /*}*/ | |||||
| #selectTD { | |||||
| width:200px; | |||||
| } | |||||
| div.typeButtons { | |||||
| height:50px; | |||||
| width:100%; | |||||
| overflow:hidden; | |||||
| } | |||||
| div.typeButton { | |||||
| display:inline-block; | |||||
| width:150px; | |||||
| height:24px; | |||||
| border:2px solid #ffffff; | |||||
| border-radius: 40px; | |||||
| text-align:center; | |||||
| font-size:12px; | |||||
| color: #848484; | |||||
| padding-top:8px; | |||||
| background-color: #dedede; | |||||
| -webkit-touch-callout: none; | |||||
| -webkit-user-select: none; | |||||
| -khtml-user-select: none; | |||||
| -moz-user-select: none; | |||||
| -ms-user-select: none; | |||||
| user-select: none; | |||||
| cursor:pointer; | |||||
| margin:5px; | |||||
| box-shadow: rgba(50,50,50,0.3) 0px 0px 3px; | |||||
| } | |||||
| div.typeButton.selected { | |||||
| background-color: #9aff4d; | |||||
| color: #000000; | |||||
| } | |||||
| div.toggleButton { | |||||
| background-image: url("../images/toggleOff.png"); | |||||
| background-repeat: no-repeat; | |||||
| background-position: 2px 2px; | |||||
| display:inline-block; | |||||
| width:138px; | |||||
| height:24px; | |||||
| font-size:12px; | |||||
| border:1px solid #d0d0d0; | |||||
| text-align:left; | |||||
| padding-top:10px; | |||||
| padding-left:60px; | |||||
| padding-right:0px; | |||||
| border-radius: 40px; | |||||
| background-color: #ffffff; | |||||
| -webkit-touch-callout: none; | |||||
| -webkit-user-select: none; | |||||
| -khtml-user-select: none; | |||||
| -moz-user-select: none; | |||||
| -ms-user-select: none; | |||||
| user-select: none; | |||||
| cursor:pointer; | |||||
| } | |||||
| div.toggleButton.square{ | |||||
| height:28px; | |||||
| border-radius: 0px; | |||||
| } | |||||
| div.toggleButton.selected { | |||||
| background-image: url("../images/toggleOn.png"); | |||||
| background-color: #ffffff; | |||||
| } | |||||
| div.graph2dContent{ | |||||
| height:450px; | |||||
| width:100%; | |||||
| } | |||||
| #graphWrapper { | |||||
| display:none; | |||||
| border: 1px solid #dddddd; | |||||
| padding:5px; | |||||
| } | |||||
| #timelineWrapper { | |||||
| display:block; | |||||
| border: 1px solid #dddddd; | |||||
| padding:5px; | |||||
| } | |||||
| .prediction_original { | |||||
| fill: rgb(123, 199, 255); | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: rgb(123, 199, 255); | |||||
| } | |||||
| .prediction { | |||||
| fill: #77da2e; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #77da2e; | |||||
| } | |||||
| .duration { | |||||
| fill: rgb(170, 34, 206); | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: rgb(170, 34, 206); | |||||
| } | |||||
| .fill { | |||||
| fill-opacity:0.1; | |||||
| stroke: none; | |||||
| } | |||||
| .bar { | |||||
| fill-opacity:0.5; | |||||
| stroke-width:1px; | |||||
| } | |||||
| .point { | |||||
| stroke-width:2px; | |||||
| fill-opacity:1.0; | |||||
| } | |||||
| .legendBackground { | |||||
| stroke-width:1px; | |||||
| fill-opacity:0.9; | |||||
| fill: #ffffff; | |||||
| stroke: #c2c2c2; | |||||
| } | |||||
| .outline { | |||||
| stroke-width:1px; | |||||
| fill-opacity:1; | |||||
| fill: #ffffff; | |||||
| stroke: #e5e5e5; | |||||
| } | |||||
| .iconFill { | |||||
| fill-opacity:0.3; | |||||
| stroke: none; | |||||
| } | |||||
| div.descriptionContainer { | |||||
| float:left; | |||||
| height:30px; | |||||
| min-width:160px; | |||||
| padding-left:5px; | |||||
| padding-right:5px; | |||||
| line-height: 30px; | |||||
| } | |||||
| div.iconContainer { | |||||
| float:left; | |||||
| } | |||||
| div.legendElementContainer { | |||||
| display:inline-block; | |||||
| height:30px; | |||||
| border-style:solid; | |||||
| border-width:1px; | |||||
| border-color: #d0d0d0; | |||||
| background-color: #ffffff; | |||||
| margin-right:6px; | |||||
| padding:4px; | |||||
| -webkit-touch-callout: none; | |||||
| -webkit-user-select: none; | |||||
| -khtml-user-select: none; | |||||
| -moz-user-select: none; | |||||
| -ms-user-select: none; | |||||
| user-select: none; | |||||
| cursor:pointer; | |||||
| } | |||||
| svg.legendIcon { | |||||
| width:30px; | |||||
| height:30px; | |||||
| } | |||||
| div.externalLegend { | |||||
| position:relative; | |||||
| top:1px; | |||||
| width: 700px; | |||||
| } | |||||
| .differencePositive { | |||||
| fill: #afff33; | |||||
| stroke-width:2px; | |||||
| stroke: #96db22; | |||||
| } | |||||
| .differenceNegative { | |||||
| fill: #ff9f00; | |||||
| stroke-width:2px; | |||||
| stroke: #e58f00; | |||||
| } | |||||
| @ -0,0 +1,745 @@ | |||||
| .vis .overlay { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| /* Must be displayed above for example selected Timeline items */ | |||||
| z-index: 10; | |||||
| } | |||||
| .vis-active { | |||||
| box-shadow: 0 0 10px #86d5f8; | |||||
| } | |||||
| .vis.timeline { | |||||
| } | |||||
| .vis.timeline.root { | |||||
| position: relative; | |||||
| border: 1px solid #bfbfbf; | |||||
| overflow: hidden; | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| box-sizing: border-box; | |||||
| } | |||||
| .vis.timeline .vispanel { | |||||
| position: absolute; | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| box-sizing: border-box; | |||||
| } | |||||
| .vis.timeline .vispanel.center, | |||||
| .vis.timeline .vispanel.left, | |||||
| .vis.timeline .vispanel.right, | |||||
| .vis.timeline .vispanel.top, | |||||
| .vis.timeline .vispanel.bottom { | |||||
| border: 1px #bfbfbf; | |||||
| } | |||||
| .vis.timeline .vispanel.center, | |||||
| .vis.timeline .vispanel.left, | |||||
| .vis.timeline .vispanel.right { | |||||
| border-top-style: solid; | |||||
| border-bottom-style: solid; | |||||
| overflow: hidden; | |||||
| } | |||||
| .vis.timeline .vispanel.center, | |||||
| .vis.timeline .vispanel.top, | |||||
| .vis.timeline .vispanel.bottom { | |||||
| border-left-style: solid; | |||||
| border-right-style: solid; | |||||
| } | |||||
| .vis.timeline .background { | |||||
| overflow: hidden; | |||||
| } | |||||
| .vis.timeline .vispanel > .content { | |||||
| position: relative; | |||||
| } | |||||
| .vis.timeline .vispanel .shadow { | |||||
| position: absolute; | |||||
| width: 100%; | |||||
| height: 1px; | |||||
| box-shadow: 0 0 10px rgba(0,0,0,0.8); | |||||
| /* TODO: find a nice way to ensure shadows are drawn on top of items | |||||
| z-index: 1; | |||||
| */ | |||||
| } | |||||
| .vis.timeline .vispanel .shadow.top { | |||||
| top: -1px; | |||||
| left: 0; | |||||
| } | |||||
| .vis.timeline .vispanel .shadow.bottom { | |||||
| bottom: -1px; | |||||
| left: 0; | |||||
| } | |||||
| .vis.timeline .labelset { | |||||
| position: relative; | |||||
| width: 100%; | |||||
| overflow: hidden; | |||||
| box-sizing: border-box; | |||||
| } | |||||
| .vis.timeline .labelset .vlabel { | |||||
| position: relative; | |||||
| left: 0; | |||||
| top: 0; | |||||
| width: 100%; | |||||
| color: #4d4d4d; | |||||
| box-sizing: border-box; | |||||
| } | |||||
| .vis.timeline .labelset .vlabel { | |||||
| border-bottom: 1px solid #bfbfbf; | |||||
| } | |||||
| .vis.timeline .labelset .vlabel:last-child { | |||||
| border-bottom: none; | |||||
| } | |||||
| .vis.timeline .labelset .vlabel .inner { | |||||
| display: inline-block; | |||||
| padding: 5px; | |||||
| } | |||||
| .vis.timeline .labelset .vlabel .inner.hidden { | |||||
| padding: 0; | |||||
| } | |||||
| .vis.timeline .itemset { | |||||
| position: relative; | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| box-sizing: border-box; | |||||
| } | |||||
| .vis.timeline .itemset .background, | |||||
| .vis.timeline .itemset .foreground { | |||||
| position: absolute; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| overflow: visible; | |||||
| } | |||||
| .vis.timeline .axis { | |||||
| position: absolute; | |||||
| width: 100%; | |||||
| height: 0; | |||||
| left: 0; | |||||
| z-index: 1; | |||||
| } | |||||
| .vis.timeline .foreground .group { | |||||
| position: relative; | |||||
| box-sizing: border-box; | |||||
| border-bottom: 1px solid #bfbfbf; | |||||
| } | |||||
| .vis.timeline .foreground .group:last-child { | |||||
| border-bottom: none; | |||||
| } | |||||
| .vis.timeline .item { | |||||
| position: absolute; | |||||
| color: #1A1A1A; | |||||
| border-color: #97B0F8; | |||||
| border-width: 1px; | |||||
| background-color: #D5DDF6; | |||||
| display: inline-block; | |||||
| padding: 5px; | |||||
| } | |||||
| .vis.timeline .item.selected { | |||||
| border-color: #FFC200; | |||||
| background-color: #FFF785; | |||||
| /* z-index must be higher than the z-index of custom time bar and current time bar */ | |||||
| z-index: 2; | |||||
| } | |||||
| .vis.timeline .editable .item.selected { | |||||
| cursor: move; | |||||
| } | |||||
| .vis.timeline .item.point.selected { | |||||
| background-color: #FFF785; | |||||
| } | |||||
| .vis.timeline .item.box { | |||||
| text-align: center; | |||||
| border-style: solid; | |||||
| border-radius: 2px; | |||||
| } | |||||
| .vis.timeline .item.point { | |||||
| background: none; | |||||
| } | |||||
| .vis.timeline .item.dot { | |||||
| position: absolute; | |||||
| padding: 0; | |||||
| border-width: 4px; | |||||
| border-style: solid; | |||||
| border-radius: 4px; | |||||
| } | |||||
| .vis.timeline .item.range { | |||||
| border-style: solid; | |||||
| border-radius: 2px; | |||||
| box-sizing: border-box; | |||||
| } | |||||
| .vis.timeline .item.background { | |||||
| overflow: hidden; | |||||
| border: none; | |||||
| background-color: rgba(213, 221, 246, 0.4); | |||||
| box-sizing: border-box; | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| } | |||||
| .vis.timeline .item.range .content { | |||||
| position: relative; | |||||
| display: inline-block; | |||||
| overflow: hidden; | |||||
| max-width: 100%; | |||||
| } | |||||
| .vis.timeline .item.background .content { | |||||
| position: absolute; | |||||
| display: inline-block; | |||||
| overflow: hidden; | |||||
| max-width: 100%; | |||||
| margin: 5px; | |||||
| } | |||||
| .vis.timeline .item.line { | |||||
| padding: 0; | |||||
| position: absolute; | |||||
| width: 0; | |||||
| border-left-width: 1px; | |||||
| border-left-style: solid; | |||||
| } | |||||
| .vis.timeline .item .content { | |||||
| white-space: nowrap; | |||||
| overflow: hidden; | |||||
| } | |||||
| .vis.timeline .item .delete { | |||||
| background: url('img/timeline/delete.png') no-repeat top center; | |||||
| position: absolute; | |||||
| width: 24px; | |||||
| height: 24px; | |||||
| top: 0; | |||||
| right: -24px; | |||||
| cursor: pointer; | |||||
| } | |||||
| .vis.timeline .item.range .drag-left { | |||||
| position: absolute; | |||||
| width: 24px; | |||||
| height: 100%; | |||||
| top: 0; | |||||
| left: -4px; | |||||
| cursor: w-resize; | |||||
| } | |||||
| .vis.timeline .item.range .drag-right { | |||||
| position: absolute; | |||||
| width: 24px; | |||||
| height: 100%; | |||||
| top: 0; | |||||
| right: -4px; | |||||
| cursor: e-resize; | |||||
| } | |||||
| .vis.timeline .timeaxis { | |||||
| position: relative; | |||||
| overflow: hidden; | |||||
| } | |||||
| .vis.timeline .timeaxis.foreground { | |||||
| top: 0; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| } | |||||
| .vis.timeline .timeaxis.background { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | |||||
| .vis.timeline .timeaxis .text { | |||||
| position: absolute; | |||||
| color: #4d4d4d; | |||||
| padding: 3px; | |||||
| white-space: nowrap; | |||||
| } | |||||
| .vis.timeline .timeaxis .text.measure { | |||||
| position: absolute; | |||||
| padding-left: 0; | |||||
| padding-right: 0; | |||||
| margin-left: 0; | |||||
| margin-right: 0; | |||||
| visibility: hidden; | |||||
| } | |||||
| .vis.timeline .timeaxis .grid.vertical { | |||||
| position: absolute; | |||||
| width: 0; | |||||
| border-right: 1px solid; | |||||
| } | |||||
| .vis.timeline .timeaxis .grid.minor { | |||||
| border-color: #e5e5e5; | |||||
| } | |||||
| .vis.timeline .timeaxis .grid.major { | |||||
| border-color: #bfbfbf; | |||||
| } | |||||
| .vis.timeline .currenttime { | |||||
| background-color: #FF7F6E; | |||||
| width: 2px; | |||||
| z-index: 1; | |||||
| } | |||||
| .vis.timeline .customtime { | |||||
| background-color: #6E94FF; | |||||
| width: 2px; | |||||
| cursor: move; | |||||
| z-index: 1; | |||||
| } | |||||
| .vis.timeline.root { | |||||
| /* | |||||
| -webkit-transition: height .4s ease-in-out; | |||||
| transition: height .4s ease-in-out; | |||||
| */ | |||||
| } | |||||
| .vis.timeline .vispanel { | |||||
| /* | |||||
| -webkit-transition: height .4s ease-in-out, top .4s ease-in-out; | |||||
| transition: height .4s ease-in-out, top .4s ease-in-out; | |||||
| */ | |||||
| } | |||||
| .vis.timeline .axis { | |||||
| /* | |||||
| -webkit-transition: top .4s ease-in-out; | |||||
| transition: top .4s ease-in-out; | |||||
| */ | |||||
| } | |||||
| /* TODO: get animation working nicely | |||||
| .vis.timeline .item { | |||||
| -webkit-transition: top .4s ease-in-out; | |||||
| transition: top .4s ease-in-out; | |||||
| } | |||||
| .vis.timeline .item.line { | |||||
| -webkit-transition: height .4s ease-in-out, top .4s ease-in-out; | |||||
| transition: height .4s ease-in-out, top .4s ease-in-out; | |||||
| } | |||||
| /**/ | |||||
| .vis.timeline .vispanel.background.horizontal .grid.horizontal { | |||||
| position: absolute; | |||||
| width: 100%; | |||||
| height: 0; | |||||
| border-bottom: 1px solid; | |||||
| } | |||||
| .vis.timeline .vispanel.background.horizontal .grid.minor { | |||||
| border-color: #e5e5e5; | |||||
| } | |||||
| .vis.timeline .vispanel.background.horizontal .grid.major { | |||||
| border-color: #bfbfbf; | |||||
| } | |||||
| .vis.timeline .dataaxis .yAxis.major { | |||||
| width: 100%; | |||||
| position: absolute; | |||||
| color: #4d4d4d; | |||||
| white-space: nowrap; | |||||
| } | |||||
| .vis.timeline .dataaxis .yAxis.major.measure{ | |||||
| padding: 0px 0px 0px 0px; | |||||
| margin: 0px 0px 0px 0px; | |||||
| visibility: hidden; | |||||
| width: auto; | |||||
| } | |||||
| .vis.timeline .dataaxis .yAxis.minor{ | |||||
| position: absolute; | |||||
| width: 100%; | |||||
| color: #bebebe; | |||||
| white-space: nowrap; | |||||
| } | |||||
| .vis.timeline .dataaxis .yAxis.minor.measure{ | |||||
| padding: 0px 0px 0px 0px; | |||||
| margin: 0px 0px 0px 0px; | |||||
| visibility: hidden; | |||||
| width: auto; | |||||
| } | |||||
| .vis.timeline .legend { | |||||
| background-color: rgba(247, 252, 255, 0.65); | |||||
| padding: 5px; | |||||
| border-color: #b3b3b3; | |||||
| border-style:solid; | |||||
| border-width: 1px; | |||||
| box-shadow: 2px 2px 10px rgba(154, 154, 154, 0.55); | |||||
| } | |||||
| .vis.timeline .legendText { | |||||
| /*font-size: 10px;*/ | |||||
| white-space: nowrap; | |||||
| display: inline-block | |||||
| } | |||||
| .vis.timeline .graphGroup0 { | |||||
| fill:#4f81bd; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #4f81bd; | |||||
| } | |||||
| .vis.timeline .graphGroup1 { | |||||
| fill:#f79646; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #f79646; | |||||
| } | |||||
| .vis.timeline .graphGroup2 { | |||||
| fill: #8c51cf; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #8c51cf; | |||||
| } | |||||
| .vis.timeline .graphGroup3 { | |||||
| fill: #75c841; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #75c841; | |||||
| } | |||||
| .vis.timeline .graphGroup4 { | |||||
| fill: #ff0100; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #ff0100; | |||||
| } | |||||
| .vis.timeline .graphGroup5 { | |||||
| fill: #37d8e6; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #37d8e6; | |||||
| } | |||||
| .vis.timeline .graphGroup6 { | |||||
| fill: #042662; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #042662; | |||||
| } | |||||
| .vis.timeline .graphGroup7 { | |||||
| fill:#00ff26; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #00ff26; | |||||
| } | |||||
| .vis.timeline .graphGroup8 { | |||||
| fill:#ff00ff; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #ff00ff; | |||||
| } | |||||
| .vis.timeline .graphGroup9 { | |||||
| fill: #8f3938; | |||||
| fill-opacity:0; | |||||
| stroke-width:2px; | |||||
| stroke: #8f3938; | |||||
| } | |||||
| .vis.timeline .fill { | |||||
| fill-opacity:0.1; | |||||
| stroke: none; | |||||
| } | |||||
| .vis.timeline .bar { | |||||
| fill-opacity:0.5; | |||||
| stroke-width:1px; | |||||
| } | |||||
| .vis.timeline .point { | |||||
| stroke-width:2px; | |||||
| fill-opacity:1.0; | |||||
| } | |||||
| .vis.timeline .legendBackground { | |||||
| stroke-width:1px; | |||||
| fill-opacity:0.9; | |||||
| fill: #ffffff; | |||||
| stroke: #c2c2c2; | |||||
| } | |||||
| .vis.timeline .outline { | |||||
| stroke-width:1px; | |||||
| fill-opacity:1; | |||||
| fill: #ffffff; | |||||
| stroke: #e5e5e5; | |||||
| } | |||||
| .vis.timeline .iconFill { | |||||
| fill-opacity:0.3; | |||||
| stroke: none; | |||||
| } | |||||
| div.network-manipulationDiv { | |||||
| border-width: 0; | |||||
| border-bottom: 1px; | |||||
| border-style:solid; | |||||
| border-color: #d6d9d8; | |||||
| background: #ffffff; /* Old browsers */ | |||||
| background: -moz-linear-gradient(top, #ffffff 0%, #fcfcfc 48%, #fafafa 50%, #fcfcfc 100%); /* FF3.6+ */ | |||||
| background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(48%,#fcfcfc), color-stop(50%,#fafafa), color-stop(100%,#fcfcfc)); /* Chrome,Safari4+ */ | |||||
| background: -webkit-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Chrome10+,Safari5.1+ */ | |||||
| background: -o-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Opera 11.10+ */ | |||||
| background: -ms-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* IE10+ */ | |||||
| background: linear-gradient(to bottom, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* W3C */ | |||||
| filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc',GradientType=0 ); /* IE6-9 */ | |||||
| position: absolute; | |||||
| left: 0; | |||||
| top: 0; | |||||
| width: 100%; | |||||
| height: 30px; | |||||
| } | |||||
| div.network-manipulation-editMode { | |||||
| position:absolute; | |||||
| left: 0; | |||||
| top: 0; | |||||
| height: 30px; | |||||
| margin-top:20px; | |||||
| } | |||||
| div.network-manipulation-closeDiv { | |||||
| position:absolute; | |||||
| right: 0; | |||||
| top: 0; | |||||
| width: 30px; | |||||
| height: 30px; | |||||
| background-position: 20px 3px; | |||||
| background-repeat: no-repeat; | |||||
| background-image: url("img/network/cross.png"); | |||||
| cursor: pointer; | |||||
| -webkit-touch-callout: none; | |||||
| -webkit-user-select: none; | |||||
| -khtml-user-select: none; | |||||
| -moz-user-select: none; | |||||
| -ms-user-select: none; | |||||
| user-select: none; | |||||
| } | |||||
| div.network-manipulation-closeDiv:hover { | |||||
| opacity: 0.6; | |||||
| } | |||||
| span.network-manipulationUI { | |||||
| font-family: verdana; | |||||
| font-size: 12px; | |||||
| -moz-border-radius: 15px; | |||||
| border-radius: 15px; | |||||
| display:inline-block; | |||||
| background-position: 0px 0px; | |||||
| background-repeat:no-repeat; | |||||
| height:24px; | |||||
| margin: -14px 0px 0px 10px; | |||||
| vertical-align:middle; | |||||
| cursor: pointer; | |||||
| padding: 0px 8px 0px 8px; | |||||
| -webkit-touch-callout: none; | |||||
| -webkit-user-select: none; | |||||
| -khtml-user-select: none; | |||||
| -moz-user-select: none; | |||||
| -ms-user-select: none; | |||||
| user-select: none; | |||||
| } | |||||
| span.network-manipulationUI:hover { | |||||
| box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.20); | |||||
| } | |||||
| span.network-manipulationUI:active { | |||||
| box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.50); | |||||
| } | |||||
| span.network-manipulationUI.back { | |||||
| background-image: url("img/network/backIcon.png"); | |||||
| } | |||||
| span.network-manipulationUI.none:hover { | |||||
| box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); | |||||
| cursor: default; | |||||
| } | |||||
| span.network-manipulationUI.none:active { | |||||
| box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); | |||||
| } | |||||
| span.network-manipulationUI.none { | |||||
| padding: 0; | |||||
| } | |||||
| span.network-manipulationUI.notification{ | |||||
| margin: 2px; | |||||
| font-weight: bold; | |||||
| } | |||||
| span.network-manipulationUI.add { | |||||
| background-image: url("img/network/addNodeIcon.png"); | |||||
| } | |||||
| span.network-manipulationUI.edit { | |||||
| background-image: url("img/network/editIcon.png"); | |||||
| } | |||||
| span.network-manipulationUI.edit.editmode { | |||||
| background-color: #fcfcfc; | |||||
| border-style:solid; | |||||
| border-width:1px; | |||||
| border-color: #cccccc; | |||||
| } | |||||
| span.network-manipulationUI.connect { | |||||
| background-image: url("img/network/connectIcon.png"); | |||||
| } | |||||
| span.network-manipulationUI.delete { | |||||
| background-image: url("img/network/deleteIcon.png"); | |||||
| } | |||||
| /* top right bottom left */ | |||||
| span.network-manipulationLabel { | |||||
| margin: 0px 0px 0px 23px; | |||||
| line-height: 25px; | |||||
| } | |||||
| div.network-seperatorLine { | |||||
| display:inline-block; | |||||
| width:1px; | |||||
| height:20px; | |||||
| background-color: #bdbdbd; | |||||
| margin: 5px 7px 0px 15px; | |||||
| } | |||||
| div.network-navigation_wrapper { | |||||
| position: absolute; | |||||
| left: 0; | |||||
| top: 0; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | |||||
| div.network-navigation { | |||||
| width:34px; | |||||
| height:34px; | |||||
| -moz-border-radius: 17px; | |||||
| border-radius: 17px; | |||||
| position:absolute; | |||||
| display:inline-block; | |||||
| background-position: 2px 2px; | |||||
| background-repeat:no-repeat; | |||||
| cursor: pointer; | |||||
| -webkit-touch-callout: none; | |||||
| -webkit-user-select: none; | |||||
| -khtml-user-select: none; | |||||
| -moz-user-select: none; | |||||
| -ms-user-select: none; | |||||
| user-select: none; | |||||
| } | |||||
| div.network-navigation:hover { | |||||
| box-shadow: 0px 0px 3px 3px rgba(56, 207, 21, 0.30); | |||||
| } | |||||
| div.network-navigation:active { | |||||
| box-shadow: 0px 0px 1px 3px rgba(56, 207, 21, 0.95); | |||||
| } | |||||
| div.network-navigation.up { | |||||
| background-image: url("img/network/upArrow.png"); | |||||
| bottom:50px; | |||||
| left:55px; | |||||
| } | |||||
| div.network-navigation.down { | |||||
| background-image: url("img/network/downArrow.png"); | |||||
| bottom:10px; | |||||
| left:55px; | |||||
| } | |||||
| div.network-navigation.left { | |||||
| background-image: url("img/network/leftArrow.png"); | |||||
| bottom:10px; | |||||
| left:15px; | |||||
| } | |||||
| div.network-navigation.right { | |||||
| background-image: url("img/network/rightArrow.png"); | |||||
| bottom:10px; | |||||
| left:95px; | |||||
| } | |||||
| div.network-navigation.zoomIn { | |||||
| background-image: url("img/network/plus.png"); | |||||
| bottom:10px; | |||||
| right:15px; | |||||
| } | |||||
| div.network-navigation.zoomOut { | |||||
| background-image: url("img/network/minus.png"); | |||||
| bottom:10px; | |||||
| right:55px; | |||||
| } | |||||
| div.network-navigation.zoomExtends { | |||||
| background-image: url("img/network/zoomExtends.png"); | |||||
| bottom:50px; | |||||
| right:15px; | |||||
| } | |||||
| @ -0,0 +1,86 @@ | |||||
| <!doctype html> | |||||
| <html> | |||||
| <head> | |||||
| <title>ARUM - MIDAS</title> | |||||
| <link href="./css/global.css" rel="stylesheet" type="text/css" /> | |||||
| <link href="./css/vis.css" rel="stylesheet" type="text/css" /> | |||||
| <script language="javascript"> | |||||
| var EVENT_DELAY = 1000; | |||||
| var EVENTS_AGENT_ADDRESS = 'ws://localhost:8082/ws/events'; | |||||
| var AMOUNT_OF_INITIAL_EVENTS = 1; | |||||
| var TIMELINE_START = '2014-09-18 10:00:00'; | |||||
| var TIMELINE_END = '2014-09-19 18:00:00'; | |||||
| var INCREASE_SPEED = true; | |||||
| var ONLINE_ONLY = true; | |||||
| </script> | |||||
| <script type="text/javascript" src="./js/vis.js"></script> | |||||
| <script type="text/javascript" src="./js/evejs.js"></script> | |||||
| <script type="text/javascript" src="./arumAgents/job.js"></script> | |||||
| <script type="text/javascript" src="./arumAgents/durationStats.js"></script> | |||||
| <script type="text/javascript" src="./arumAgents/durationData.js"></script> | |||||
| <script type="text/javascript" src="./arumAgents/jobManager.js"></script> | |||||
| <script type="text/javascript" src="./arumAgents/jobAgent.js"></script> | |||||
| <script type="text/javascript" src="./js/vis_eve_init.js"></script> | |||||
| <script type="text/javascript" src="./arumAgents/genericAgent.js"></script> | |||||
| <script type="text/javascript" src="./arumAgents/agentGenerator.js"></script> | |||||
| <script type="text/javascript" src="./arumAgents/jobAgentGenerator.js"></script> | |||||
| <script type="text/javascript" src="./arumAgents/eventGenerator.js"></script> | |||||
| <script type="text/javascript" src="./js/init.js"></script> | |||||
| </head> | |||||
| <body onload="draw()"> | |||||
| <img src="./images/arum_small.png" style="position:relative; top:-6px;"><img src="./images/midas_small.png" style="float:right;"><br> | |||||
| <div class="typeButton selected" onclick="showTimelineBtn()" id="showTimeline">Show by Timeline</div> | |||||
| <div class="typeButton" onclick="showGraphBtn()" id="showGraph">Show by Job</div> | |||||
| <div id="timelineWrapper"> | |||||
| <input type="button" value="Next Event" onclick="getNewEvent()"> | |||||
| eventNo:<div id="eventCounter" style="display:inline;">-1</div> | |||||
| <input type="button" value="Next 10 Events" onclick="getTenNewEvents()"> | |||||
| <input type="button" value="Get All Events" onclick="getAllEvents()"> | |||||
| <input type="button" value="Get All Events Quickly" onclick="getAllEventsQuickly()"> <br> | |||||
| <div id="timeline"></div> | |||||
| </div> | |||||
| <div id="graphWrapper"> | |||||
| <table class="wrapper"> | |||||
| <tr> | |||||
| <td> | |||||
| <div class="toggleButton" onclick="togglePrediction()" id="togglePrediction">Prediction</div> | |||||
| </td> | |||||
| <td width="6px"> </td> | |||||
| <td> | |||||
| <div class="typeButton selected" onclick="turnOn('duration')" id="duration">Ideal</div> | |||||
| <div class="typeButton" onclick="turnOn('durationWithPause')" id="durationWithPause">Ideal + Pause</div> | |||||
| <div class="typeButton" onclick="turnOn('durationWithStartup')" id="durationWithStartup">Ideal + Prerequisites</div> | |||||
| <div class="typeButton" onclick="turnOn('durationWithBoth')" id="durationWithBoth">Total Time</div> | |||||
| </td> | |||||
| </tr> | |||||
| <tr> | |||||
| <td id="selectTD"> | |||||
| <select name="jobs" id="multiselect" multiple onclick="updateGraph()"></select> | |||||
| </td> | |||||
| <td colspan="2"> | |||||
| <div class="graph2dContent"> | |||||
| <div id="graph2d"></div> | |||||
| </div> | |||||
| </td> | |||||
| </tr> | |||||
| <tr> | |||||
| <td> | |||||
| <div class="toggleButton square" onclick="toggleDifference()" id="difference">Switch View</div> | |||||
| </td> | |||||
| <td colspan="2"> | |||||
| <div id="Legend" class="externalLegend"></div> | |||||
| </td> | |||||
| </tr> | |||||
| </table> | |||||
| </div> | |||||
| <p id="selection"></p> | |||||
| <p id="stabilization"></p> | |||||
| </body> | |||||
| </html> | |||||
| @ -0,0 +1,350 @@ | |||||
| /** | |||||
| * Created by Alex on 10/27/2014. | |||||
| */ | |||||
| var GETTING_EVENTS = false; | |||||
| var agentList = {}; | |||||
| var jobList = {}; | |||||
| var eventGen = new EventGenerator("eventGenerator"); | |||||
| var agentGen = new AgentGenerator("agentGenerator"); | |||||
| var jobGen = new JobAgentGenerator("jobAgentGenerator"); | |||||
| // eventGen.start(); | |||||
| function getNewEvent() { | |||||
| agentGen.getEvents(1); | |||||
| } | |||||
| function getTenNewEvents() { | |||||
| agentGen.getEvents(10); | |||||
| } | |||||
| function getAllEvents() { | |||||
| if (GETTING_EVENTS == false) { | |||||
| GETTING_EVENTS = true; | |||||
| agentGen.getEvents(agentGen.amountOfEvents); | |||||
| } | |||||
| } | |||||
| function getAllEventsQuickly() { | |||||
| INCREASE_SPEED = false; | |||||
| EVENT_DELAY = 0; | |||||
| if (GETTING_EVENTS == false) { | |||||
| GETTING_EVENTS = true; | |||||
| agentGen.getEvents(agentGen.amountOfEvents); | |||||
| } | |||||
| else { | |||||
| } | |||||
| } | |||||
| function refreshJobs() { | |||||
| var multiSelect = document.getElementById("multiselect"); | |||||
| while (multiSelect.firstChild) { | |||||
| multiSelect.removeChild(multiSelect.firstChild); | |||||
| } | |||||
| var jobNames = jobGen.getAllJobNames(); | |||||
| for (var i = 0; i < jobNames.length; i++) { | |||||
| var jobOption = new Option(jobNames[i], jobNames[i]); | |||||
| multiSelect.appendChild(jobOption); | |||||
| } | |||||
| } | |||||
| function updateGraph() { | |||||
| var multiSelect = document.getElementById("multiselect"); | |||||
| var selection = []; | |||||
| for (var i = 0; i < multiSelect.children.length; i++) { | |||||
| if (multiSelect.children[i].selected) { | |||||
| selection.push(multiSelect.children[i].value); | |||||
| } | |||||
| } | |||||
| var filteredValues = []; | |||||
| var originalPredictionValues = []; | |||||
| var durationValues = []; | |||||
| var predictionValues = []; | |||||
| var diffValues = []; | |||||
| var graphGroup = null; | |||||
| var stdGroup = null; | |||||
| var predictionGroup = null; | |||||
| var originalPredictionGroup = null; | |||||
| for (var i = 0; i < graph2dDataset.length; i++) { | |||||
| if (selection.indexOf(graph2dDataset[i].type) != -1) { | |||||
| if (showDuration == true) { | |||||
| if (graph2dDataset[i].group == graph2dDataset[i].type + "_" + selectedGroup) { | |||||
| filteredValues.push(graph2dDataset[i]); | |||||
| durationValues.push(graph2dDataset[i]); | |||||
| graphGroup = graph2dDataset[i].group; | |||||
| } | |||||
| } | |||||
| if (showPrediction == true) { | |||||
| if (graph2dDataset[i].group == graph2dDataset[i].type + "_pred_" + selectedGroup) { | |||||
| filteredValues.push(graph2dDataset[i]); | |||||
| predictionValues.push(graph2dDataset[i]); | |||||
| predictionGroup = graph2dDataset[i].group; | |||||
| } | |||||
| if (graph2dDataset[i].group == graph2dDataset[i].type + "_pred_" + selectedGroup + "_std_higher") { | |||||
| filteredValues.push(graph2dDataset[i]); | |||||
| stdGroup = graph2dDataset[i].group; | |||||
| } | |||||
| if (graph2dDataset[i].group == graph2dDataset[i].type + "_pred_" + selectedGroup + "_std_lower") { | |||||
| filteredValues.push(graph2dDataset[i]); | |||||
| stdGroup = graph2dDataset[i].group; | |||||
| } | |||||
| if (graph2dDataset[i].group == graph2dDataset[i].type + "_pred_" + selectedGroup + "_original") { | |||||
| filteredValues.push(graph2dDataset[i]); | |||||
| originalPredictionValues.push(graph2dDataset[i]); | |||||
| originalPredictionGroup = graph2dDataset[i].group; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| graph2DItems.clear(); | |||||
| if (differenceWithPrediction == true) { | |||||
| for (var i = 0; i < durationValues.length; i++) { | |||||
| var item = {}; | |||||
| item.x = i < 10 ? '2014-09-0' + i : '2014-09-' + i; | |||||
| item.y = durationValues[i].y; | |||||
| item.type = durationValues[i].type; | |||||
| item.y -= originalPredictionValues[i].y; | |||||
| var group = 'differenceNegative'; | |||||
| if (item.y < 0) { | |||||
| item.y *= -1; | |||||
| group = 'differencePositive' | |||||
| } | |||||
| item.group = group; | |||||
| diffValues.push(item); | |||||
| } | |||||
| var legendDiv = document.getElementById("Legend"); | |||||
| legendDiv.innerHTML = ""; | |||||
| populateExternalLegend('differencePositive', "Faster than predicted (hours)"); | |||||
| populateExternalLegend('differenceNegative', "Slower than predicted (hours)"); | |||||
| graph2DItems.add(diffValues); | |||||
| } | |||||
| else { | |||||
| var filteredValues = []; | |||||
| for (var i = 0; i < durationValues.length; i++) { | |||||
| var durationItem = {}; | |||||
| durationItem.x = i < 10 ? '2014-09-0' + i : '2014-09-' + i; | |||||
| durationItem.y = durationValues[i].y; | |||||
| durationItem.type = durationValues[i].type; | |||||
| durationItem.group = durationValues[i].group; | |||||
| filteredValues.push(durationItem); | |||||
| if (showPrediction == true) { | |||||
| var predItem = {}; | |||||
| predItem.x = i < 10 ? '2014-09-0' + i : '2014-09-' + i; | |||||
| predItem.y = predictionValues[i].y; | |||||
| predItem.type = predictionValues[i].type; | |||||
| predItem.group = predictionValues[i].group; | |||||
| filteredValues.push(predItem); | |||||
| var originalPred = {}; | |||||
| originalPred.x = i < 10 ? '2014-09-0' + i : '2014-09-' + i; | |||||
| originalPred.y = originalPredictionValues[i].y; | |||||
| originalPred.type = originalPredictionValues[i].type; | |||||
| originalPred.group = originalPredictionValues[i].group; | |||||
| filteredValues.push(originalPred); | |||||
| } | |||||
| } | |||||
| var legendDiv = document.getElementById("Legend"); | |||||
| legendDiv.innerHTML = ""; | |||||
| if (graphGroup != null) { | |||||
| populateExternalLegend(graphGroup, "duration (hours)"); | |||||
| } | |||||
| // if (stdGroup != null) {populateExternalLegend(stdGroup, "standard deviation");} | |||||
| if (predictionGroup != null) { | |||||
| populateExternalLegend(predictionGroup, "updated prediction (hours)"); | |||||
| } | |||||
| if (originalPredictionGroup != null) { | |||||
| populateExternalLegend(originalPredictionGroup, "initial prediction (hours)"); | |||||
| } | |||||
| graph2DItems.add(filteredValues); | |||||
| } | |||||
| graph2d.fit(); | |||||
| } | |||||
| function turnOff(type) { | |||||
| var btn = document.getElementById(type); | |||||
| btn.className = btn.className.replace(" selected", ""); | |||||
| } | |||||
| function turnOffAll() { | |||||
| var types = ['duration', 'durationWithPause', 'durationWithStartup', 'durationWithBoth']; | |||||
| for (var i = 0; i < types.length; i++) { | |||||
| turnOff(types[i]); | |||||
| } | |||||
| } | |||||
| function turnOn(type) { | |||||
| turnOffAll(); | |||||
| var btn = document.getElementById(type); | |||||
| selectedGroup = type; | |||||
| btn.className += " selected"; | |||||
| updateGraph(); | |||||
| } | |||||
| function togglePrediction() { | |||||
| if (differenceWithPrediction != true) { | |||||
| var btn = document.getElementById('togglePrediction'); | |||||
| if (showPrediction == true) { | |||||
| btn.className = btn.className.replace("selected", ""); | |||||
| showPrediction = false; | |||||
| } | |||||
| else { | |||||
| showPrediction = true; | |||||
| btn.className += " selected"; | |||||
| } | |||||
| updateGraph(); | |||||
| } | |||||
| } | |||||
| function toggleDuration() { | |||||
| if (differenceWithPrediction != true) { | |||||
| var btn = document.getElementById('toggleDuration'); | |||||
| if (showDuration == true) { | |||||
| btn.className = btn.className.replace("selected", ""); | |||||
| showDuration = false; | |||||
| } | |||||
| else { | |||||
| showDuration = true; | |||||
| btn.className += " selected"; | |||||
| } | |||||
| updateGraph(); | |||||
| } | |||||
| } | |||||
| function toggleDifference() { | |||||
| differenceWithPrediction = !differenceWithPrediction; | |||||
| var btn = document.getElementById('togglePrediction'); | |||||
| if (showPrediction == false && differenceWithPrediction == true) { | |||||
| showPrediction = true; | |||||
| btn.className += " selected"; | |||||
| } | |||||
| btn = document.getElementById('toggleDuration'); | |||||
| if (showDuration == false && differenceWithPrediction == true) { | |||||
| showDuration = true; | |||||
| btn.className += " selected"; | |||||
| } | |||||
| var btn2 = document.getElementById('difference'); | |||||
| if (differenceWithPrediction == false) { | |||||
| btn2.className = btn2.className.replace("selected", ""); | |||||
| } | |||||
| else { | |||||
| btn2.className += " selected"; | |||||
| } | |||||
| updateGraph(); | |||||
| } | |||||
| function showTimelineBtn() { | |||||
| var timelineBtn = document.getElementById('showTimeline'); | |||||
| var graphBtn = document.getElementById('showGraph'); | |||||
| if (showTimeline == false) { | |||||
| graphBtn.className = graphBtn.className.replace("selected", ""); | |||||
| timelineBtn.className += " selected"; | |||||
| var timelinewrapper = document.getElementById("timelineWrapper"); | |||||
| var graphwrapper = document.getElementById("graphWrapper"); | |||||
| graphwrapper.style.display = "none"; | |||||
| timelinewrapper.style.display = "block"; | |||||
| showTimeline = true; | |||||
| showGraph = false; | |||||
| } | |||||
| } | |||||
| function showGraphBtn() { | |||||
| var timelineBtn = document.getElementById('showTimeline'); | |||||
| var graphBtn = document.getElementById('showGraph'); | |||||
| if (showGraph == false) { | |||||
| timelineBtn.className = graphBtn.className.replace("selected", ""); | |||||
| graphBtn.className += " selected"; | |||||
| var timelinewrapper = document.getElementById("timelineWrapper"); | |||||
| var graphwrapper = document.getElementById("graphWrapper"); | |||||
| timelinewrapper.style.display = "none"; | |||||
| graphwrapper.style.display = "block"; | |||||
| showTimeline = false; | |||||
| showGraph = true; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * this function fills the external legend with content using the getLegend() function. | |||||
| */ | |||||
| function populateExternalLegend(groupDataItem, description) { | |||||
| var legendDiv = document.getElementById("Legend"); | |||||
| // create divs | |||||
| var containerDiv = document.createElement("div"); | |||||
| var iconDiv = document.createElement("div"); | |||||
| var descriptionDiv = document.createElement("div"); | |||||
| // give divs classes and Ids where necessary | |||||
| containerDiv.className = 'legendElementContainer'; | |||||
| containerDiv.id = groupDataItem + "_legendContainer"; | |||||
| iconDiv.className = "iconContainer"; | |||||
| descriptionDiv.className = "descriptionContainer"; | |||||
| // get the legend for this group. | |||||
| var legend = graph2d.getLegend(groupDataItem, 30, 30); | |||||
| // append class to icon. All styling classes from the vis.css have been copied over into the head here to be able to style the | |||||
| // icons with the same classes if they are using the default ones. | |||||
| legend.icon.setAttributeNS(null, "class", "legendIcon"); | |||||
| // append the legend to the corresponding divs | |||||
| iconDiv.appendChild(legend.icon); | |||||
| descriptionDiv.innerHTML = description; | |||||
| // determine the order for left and right orientation | |||||
| if (legend.orientation == 'left') { | |||||
| descriptionDiv.style.textAlign = "left"; | |||||
| containerDiv.appendChild(iconDiv); | |||||
| containerDiv.appendChild(descriptionDiv); | |||||
| } | |||||
| else { | |||||
| descriptionDiv.style.textAlign = "right"; | |||||
| containerDiv.appendChild(descriptionDiv); | |||||
| containerDiv.appendChild(iconDiv); | |||||
| } | |||||
| // append to the legend container div | |||||
| legendDiv.appendChild(containerDiv); | |||||
| } | |||||
| function printEvents(events) { | |||||
| var str = ""; | |||||
| str += "["; | |||||
| for (var i = 0; i < events.length; i++) { | |||||
| str += "{"; | |||||
| var first = true; | |||||
| for (var eventField in events[i]) { | |||||
| if (events[i].hasOwnProperty(eventField)) { | |||||
| if (first == false) { | |||||
| str += "," | |||||
| } | |||||
| first = false; | |||||
| if (eventField == "time") { | |||||
| str += eventField + ": '" + new Date(events[i][eventField]).valueOf() + "'"; | |||||
| } | |||||
| else if(events[i][eventField] instanceof Array) { | |||||
| str += eventField + ": ["; | |||||
| for (var j = 0; j < events[i][eventField].length; j++) { | |||||
| str += "'" + events[i][eventField][j] + "'"; | |||||
| if (j != events[i][eventField].length - 1) { | |||||
| str += ","; | |||||
| } | |||||
| } | |||||
| } | |||||
| else { | |||||
| str += eventField + ": '" + events[i][eventField] + "'"; | |||||
| } | |||||
| } | |||||
| } | |||||
| str += "}"; | |||||
| if (i < events.length -1) { | |||||
| str += ","; | |||||
| } | |||||
| } | |||||
| str += "]"; | |||||
| console.log(str); | |||||
| } | |||||
| @ -0,0 +1,105 @@ | |||||
| /** | |||||
| * Created by Alex on 10/27/2014. | |||||
| */ | |||||
| 'use strict'; | |||||
| var timeline; | |||||
| var timelineItems = new vis.DataSet(); | |||||
| var timelineGroups = new vis.DataSet(); | |||||
| var graph2d; | |||||
| var graph2dDataset = []; | |||||
| var graph2DItems = new vis.DataSet(); | |||||
| var graph2dGroups = new vis.DataSet(); | |||||
| graph2dGroups.add({ | |||||
| id: 'differencePositive', content: 'differencePositive', className: "differencePositive", options: { | |||||
| drawPoints: false, | |||||
| style: 'bar', | |||||
| barChart: {width: 50, align: 'center'} // align: left, center, right | |||||
| } | |||||
| }); | |||||
| graph2dGroups.add({ | |||||
| id: 'differenceNegative', content: 'differenceNegative', className: "differenceNegative", options: { | |||||
| drawPoints: false, | |||||
| style: 'bar', | |||||
| barChart: {width: 50, align: 'center'} // align: left, center, right | |||||
| } | |||||
| }); | |||||
| var eventCounter; | |||||
| var selectedGroup = 'duration'; | |||||
| var showDuration = true; | |||||
| var showPrediction = false; | |||||
| var showTimeline = true; | |||||
| var showGraph = false; | |||||
| var differenceWithPrediction = false; | |||||
| function draw() { | |||||
| eventCounter = document.getElementById('eventCounter'); | |||||
| // add items to the DataSet | |||||
| var timelineContainer = document.getElementById('timeline'); | |||||
| var timelineOptions = { | |||||
| hiddenDates: [ | |||||
| {start: '2013-10-26 00:00:00', end: '2013-10-28 00:00:00', repeat: 'weekly'}, // daily weekly monthly yearly | |||||
| {start: '2013-03-29 18:30:00', end: '2013-03-30 08:00:00', repeat: 'daily'} // daily weekly monthly yearly | |||||
| ], | |||||
| start: TIMELINE_START, | |||||
| end: TIMELINE_END, | |||||
| autoResize: false, | |||||
| showCustomTime: true, | |||||
| showCurrentTime: false, | |||||
| stack: false | |||||
| }; | |||||
| timeline = new vis.Timeline(timelineContainer, timelineItems, timelineGroups, timelineOptions); | |||||
| var graph2dContainer = document.getElementById('graph2d'); | |||||
| var graph2dOptions = { | |||||
| style:'bar', | |||||
| barChart: {width:50, align:'center', handleOverlap:'sideBySide'}, | |||||
| start: '2014-08-25', | |||||
| end: '2014-09-25', | |||||
| autoResize: false, | |||||
| // height: '450px', | |||||
| showCurrentTime: false, | |||||
| catmullRom: false, | |||||
| showMajorLabels: false, | |||||
| showMinorLabels: false, | |||||
| graphHeight:'450px', | |||||
| dataAxis: { | |||||
| customRange: { | |||||
| left: { | |||||
| min:-0.5 | |||||
| } | |||||
| } | |||||
| }, | |||||
| drawPoints:false //{style:'circle'} | |||||
| }; | |||||
| graph2d = new vis.Graph2d(graph2dContainer, graph2DItems, graph2dGroups, graph2dOptions); | |||||
| } | |||||
| window.onresize = function () { | |||||
| timeline.redraw(); | |||||
| graph2d.redraw(); | |||||
| } | |||||
| var conn; | |||||
| if (ONLINE_ONLY == true) { | |||||
| eve.system.init({ | |||||
| transports: [ | |||||
| { | |||||
| type: 'local' | |||||
| } | |||||
| ] | |||||
| }); | |||||
| } | |||||
| else { | |||||
| eve.system.init({ | |||||
| transports: [ | |||||
| { | |||||
| type: 'ws' | |||||
| } | |||||
| ] | |||||
| }); | |||||
| } | |||||