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