| @ -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' | |||
| } | |||
| ] | |||
| }); | |||
| } | |||