1
|
|
|
define(['factory', 'canvas', 'menus', 'faulttree/config', 'alerts', 'datatables', 'datatables-api', 'faulttree/node_group', 'highcharts', 'jquery-ui'], |
2
|
|
|
function(Factory, Canvas, Menus, FaulttreeConfig, Alerts, DataTables) { |
3
|
|
|
|
4
|
|
|
/** |
5
|
|
|
* Class: CutsetsMenu |
6
|
|
|
* A menu for displaying a list of minimal cutsets calculated for the edited graph. The nodes that belong to a |
7
|
|
|
* cutset become highlighted when hovering over the corresponding entry in the cutsets menu. |
8
|
|
|
* |
9
|
|
|
* Extends: <Base::Menus::Menu> |
10
|
|
|
*/ |
11
|
|
|
var CutsetsMenu = Menus.Menu.extend({ |
12
|
|
|
/** |
13
|
|
|
* Group: Members |
14
|
|
|
* {Editor} _editor - <Faulttree::Editor> the editor that owns this menu. |
15
|
|
|
*/ |
16
|
|
|
_editor: undefined, |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Group: Initialization |
20
|
|
|
*/ |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Constructor: init |
24
|
|
|
* Sets up the menu. |
25
|
|
|
* |
26
|
|
|
* Parameters: |
27
|
|
|
* {Editor} _editor - <Faulttree::Editor> the editor that owns this menu. |
28
|
|
|
*/ |
29
|
|
|
init: function(editor) { |
30
|
|
|
this._super(); |
31
|
|
|
this._editor = editor; |
32
|
|
|
}, |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Method: _setupContainer |
36
|
|
|
* Sets up the DOM container element for this menu and appends it to the DOM. |
37
|
|
|
* |
38
|
|
|
* Returns: |
39
|
|
|
* A {jQuery} set holding the container. |
40
|
|
|
*/ |
41
|
|
|
_setupContainer: function() { |
42
|
|
|
return jQuery( |
43
|
|
|
'<div id="' + Factory.getModule('Config').IDs.CUTSETS_MENU + '" class="menu" header="Cutsets">\ |
44
|
|
|
<div class="menu-controls">\ |
45
|
|
|
<i class="menu-minimize"></i>\ |
46
|
|
|
<i class="menu-close"> </i>\ |
47
|
|
|
</div>\ |
48
|
|
|
<ul class="nav-list unstyled"></ul>\ |
49
|
|
|
</div>' |
50
|
|
|
).appendTo(jQuery('#' + Factory.getModule('Config').IDs.CONTENT)); |
51
|
|
|
}, |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Group: Actions |
55
|
|
|
*/ |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Method: show |
59
|
|
|
* Display the given cutsets in the menu and make the menu visible. |
60
|
|
|
* |
61
|
|
|
* Parameters: |
62
|
|
|
* {Array[Object]} cutsets - A list of cutsets calculated by the backend. |
63
|
|
|
* |
64
|
|
|
* Returns: |
65
|
|
|
* This{<Menu>} instance for chaining. |
66
|
|
|
*/ |
67
|
|
|
show: function(cutsets) { |
68
|
|
|
if (typeof cutsets === 'undefined') { |
69
|
|
|
this.container.show(); |
70
|
|
|
return this; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
var listElement = this.container.find('ul').empty(); |
74
|
|
|
|
75
|
|
|
_.each(cutsets, function(cutset) { |
76
|
|
|
var nodeIDs = cutset['nodes']; |
77
|
|
|
var nodes = _.map(nodeIDs, function(id) { |
78
|
|
|
return this._editor.graph.getNodeById(id); |
79
|
|
|
}.bind(this)); |
80
|
|
|
var nodeNames = _.map(nodes, function(node) { |
81
|
|
|
return node.name; |
82
|
|
|
}); |
83
|
|
|
|
84
|
|
|
// create list entry for the menu |
85
|
|
|
var entry = jQuery('<li><a href="#">' + nodeNames.join(', ') + '</a></li>'); |
86
|
|
|
|
87
|
|
|
// highlight the corresponding nodes on hover |
88
|
|
|
entry.hover( |
89
|
|
|
// in |
90
|
|
|
function() { |
91
|
|
|
var disable = _.difference(this._editor.graph.getNodes(), nodes); |
92
|
|
|
_.invoke(disable, 'disable'); |
93
|
|
|
_.invoke(nodes, 'highlight'); |
94
|
|
|
}.bind(this), |
95
|
|
|
|
96
|
|
|
// out |
97
|
|
|
function() { |
98
|
|
|
var enable = _.difference(this._editor.graph.getNodes(), nodes); |
99
|
|
|
_.invoke(enable, 'enable'); |
100
|
|
|
_.invoke(nodes, 'unhighlight'); |
101
|
|
|
}.bind(this) |
102
|
|
|
); |
103
|
|
|
|
104
|
|
|
listElement.append(entry); |
105
|
|
|
}.bind(this)); |
106
|
|
|
|
107
|
|
|
this._super(); |
108
|
|
|
return this; |
109
|
|
|
} |
110
|
|
|
}); |
111
|
|
|
|
112
|
|
|
/* |
113
|
|
|
* Abstract Class: AnalysisResultMenu |
114
|
|
|
* Base class for menus that display the results of a analysis performed by the backend. It contains a chart |
115
|
|
|
* (implemented with Highcharts) and a table area (using DataTables). |
116
|
|
|
* |
117
|
|
|
*/ |
118
|
|
|
var AnalysisResultMenu = Menus.Menu.extend({ |
119
|
|
|
/** |
120
|
|
|
* Group: Members |
121
|
|
|
* {<Editor>} _editor - The <Editor> instance. |
122
|
|
|
* {<Job>} _job - <Job> instance of the backend job that is responsible for |
123
|
|
|
* calculating the probability. |
124
|
|
|
* {jQuery Selector} _graphIssuesContainer - Display |
125
|
|
|
* {jQuery Selector} _chartContainer - jQuery reference to the chart's container. |
126
|
|
|
* {jQuery Selector} _tableContainer - jQuery reference to the table's container. |
127
|
|
|
* {Highchart} _chart - The Highchart instance displaying the result. |
128
|
|
|
* {DataTables} _table - The DataTables instance displaying the result. |
129
|
|
|
* {jQuery Selector} _staticInfoContainer - Static result information from the backend, directly shown. |
130
|
|
|
* {Object} _configNodeMap - A mapping of the configuration ID to its node set. |
131
|
|
|
* {Object} _configNodeMap - A mapping of the configuration ID to its edge set. |
132
|
|
|
* {Object} _redundancyNodeMap - A mapping of the configuration ID to the nodes' N-values |
133
|
|
|
* {Object} _configMetaDataCached - A dictionary indicating if choice meta data for a specific configuration is cached. |
134
|
|
|
|
135
|
|
|
*/ |
136
|
|
|
_editor: undefined, |
137
|
|
|
_job: undefined, |
138
|
|
|
_graphIssuesContainer: undefined, |
139
|
|
|
_chartContainer: undefined, |
140
|
|
|
_tableContainer: undefined, |
141
|
|
|
_chart: undefined, |
142
|
|
|
_table: undefined, |
143
|
|
|
_staticInfoContainer: undefined, |
144
|
|
|
_configNodeMap: {}, |
145
|
|
|
_configEdgeMap: {}, |
146
|
|
|
_redundancyNodeMap: {}, |
147
|
|
|
_configMetaDataCached: {}, |
148
|
|
|
|
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Group: Initialization |
152
|
|
|
*/ |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Constructor: init |
156
|
|
|
* Sets up the menu. |
157
|
|
|
*/ |
158
|
|
|
init: function(editor) { |
159
|
|
|
this._super(); |
160
|
|
|
this._editor = editor; |
161
|
|
|
this._graphIssuesContainer = this.container.find('.graph_issues'); |
162
|
|
|
this._chartContainer = this.container.find('.chart'); |
163
|
|
|
this._tableContainer = this.container.find('.table_container'); |
164
|
|
|
this._staticInfoContainer = this.container.find('.static_info'); |
165
|
|
|
}, |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Group: Actions |
169
|
|
|
*/ |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Method: show |
173
|
|
|
* Display the given job status its results. |
174
|
|
|
* |
175
|
|
|
* Parameters: |
176
|
|
|
* {<Job>} job - The backend job that calculates the probability of the top event. |
177
|
|
|
* |
178
|
|
|
* Returns: |
179
|
|
|
* This {<AnalysisResultMenu>} instance for chaining. |
180
|
|
|
*/ |
181
|
|
|
show: function(job) { |
182
|
|
|
// clear the content |
183
|
|
|
this._clear(); |
184
|
|
|
|
185
|
|
|
job.successCallback = this._evaluateResult.bind(this); |
186
|
|
|
job.updateCallback = this._displayProgress.bind(this); |
187
|
|
|
job.errorCallback = this._displayJobError.bind(this); |
188
|
|
|
job.notFoundCallback = this._displayJobError.bind(this); |
189
|
|
|
job.queryInterval = 500; |
190
|
|
|
|
191
|
|
|
this._job = job; |
192
|
|
|
job.progressMessage = Factory.getModule('Config').ProgressIndicator.CALCULATING_MESSAGE; |
193
|
|
|
job.start(); |
194
|
|
|
|
195
|
|
|
this._super(); |
196
|
|
|
return this; |
197
|
|
|
}, |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Method: hide |
201
|
|
|
* Hide the menu and clear all its content. Also stops querying for job results. |
202
|
|
|
* |
203
|
|
|
* Returns: |
204
|
|
|
* This {<AnalysisResultMenu>} instance for chaining. |
205
|
|
|
*/ |
206
|
|
|
hide: function() { |
207
|
|
|
this._super(); |
208
|
|
|
// cancel query job |
209
|
|
|
this._job.cancel(); |
210
|
|
|
// clear content |
211
|
|
|
this._clear(); |
212
|
|
|
|
213
|
|
|
return this; |
214
|
|
|
}, |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Method: _clear |
218
|
|
|
* Clear the content of the menu and cancel any running jobs. |
219
|
|
|
* |
220
|
|
|
* Returns: |
221
|
|
|
* This {<AnalysisResultMenu>} instance for chaining. |
222
|
|
|
*/ |
223
|
|
|
_clear: function() { |
224
|
|
|
if (typeof this._job !== 'undefined') this._job.cancel(); |
225
|
|
|
|
226
|
|
|
// reset height of the chart container (which is set after resizing event) |
227
|
|
|
this._chartContainer.height(''); |
228
|
|
|
|
229
|
|
|
// reset height in case it was set during grid creation |
230
|
|
|
this._tableContainer.css('min-height', ''); |
231
|
|
|
|
232
|
|
|
// reset container width (which is set after initalisation of DataTables) |
233
|
|
|
this.container.css('width',''); |
234
|
|
|
|
235
|
|
|
this._graphIssuesContainer.empty(); |
236
|
|
|
this._chartContainer.empty(); |
237
|
|
|
this._tableContainer.empty(); |
238
|
|
|
this._chart = null; |
239
|
|
|
this._table = null; |
240
|
|
|
this._staticInfoContainer.empty(); |
241
|
|
|
this._configNodeMap = {}; |
242
|
|
|
this._redundancyNodeMap = {}; |
243
|
|
|
this._configEdgeMap = {}; |
244
|
|
|
this._configMetaDataCached = {}; |
245
|
|
|
|
246
|
|
|
return this; |
247
|
|
|
}, |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Group: Accessors |
251
|
|
|
*/ |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Abstract Method: _progressMessage |
255
|
|
|
* Should compute the message that is displayed while the backend calculation is pending. |
256
|
|
|
* |
257
|
|
|
* Returns: |
258
|
|
|
* A {String} with the message. May contain HTML. |
259
|
|
|
*/ |
260
|
|
|
_progressMessage: function() { |
261
|
|
|
throw new SubclassResponsibility(); |
262
|
|
|
}, |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Abstract Method: _menuHeader |
266
|
|
|
* Computes the header of the menu. |
267
|
|
|
* |
268
|
|
|
* Returns: |
269
|
|
|
* A {String} which is the header of the menu. |
270
|
|
|
*/ |
271
|
|
|
_menuHeader: function() { |
272
|
|
|
throw new SubclassResponsibility(); |
273
|
|
|
}, |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Method: _containerID |
277
|
|
|
* Computes the HTML ID of the container. |
278
|
|
|
* |
279
|
|
|
* Returns: |
280
|
|
|
* A {String} which is the ID of the Container. |
281
|
|
|
*/ |
282
|
|
|
_containerID: function() { |
283
|
|
|
throw new SubclassResponsibility(); |
284
|
|
|
}, |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Group: Setup |
288
|
|
|
*/ |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Abstract Method: _setupContainer |
292
|
|
|
* Sets up the DOM container element for this menu and appends it to the DOM. |
293
|
|
|
* |
294
|
|
|
* Returns: |
295
|
|
|
* A jQuery object of the container. |
296
|
|
|
*/ |
297
|
|
|
_setupContainer: function() { |
298
|
|
|
return jQuery( |
299
|
|
|
'<div id="' + this._containerID() + '" class="menu probabillity_menu" header="'+ this._menuHeader() +'">\ |
300
|
|
|
<div class="menu-controls">\ |
301
|
|
|
<i class="menu-minimize"></i>\ |
302
|
|
|
<i class="menu-close"></i>\ |
303
|
|
|
</div>\ |
304
|
|
|
<div class="graph_issues"></div>\ |
305
|
|
|
<div class="chart"></div>\ |
306
|
|
|
<div class="table_container content"></div>\ |
307
|
|
|
<div class="static_info"></div>\ |
308
|
|
|
</div>' |
309
|
|
|
).appendTo(jQuery('#' + Factory.getModule('Config').IDs.CONTENT)); |
310
|
|
|
}, |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Method: setupResizing |
314
|
|
|
* Enables this menu to be resizable and therefore to enlarge or shrink the calculated analysis results. |
315
|
|
|
* Any subclass has to ensure that its particular outcomes adhere to this behaviour. |
316
|
|
|
* |
317
|
|
|
* Returns: |
318
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
319
|
|
|
*/ |
320
|
|
|
_setupResizing: function() { |
321
|
|
|
this.container.resizable({ |
322
|
|
|
minHeight: this.container.outerHeight(), // use current height as minimum |
323
|
|
|
minWidth : this.container.outerWidth(), |
324
|
|
|
resize: function(event, ui) { |
325
|
|
|
|
326
|
|
|
if (this._chart != null) { |
327
|
|
|
|
328
|
|
|
// fit all available space with chart |
329
|
|
|
this._chartContainer.height(this.container.height() - |
330
|
|
|
this._graphIssuesContainer.height() - |
331
|
|
|
this._tableContainer.height() - |
332
|
|
|
this._staticInfoContainer.height()); |
333
|
|
|
|
334
|
|
|
this._chart.setSize( |
335
|
|
|
this._chartContainer.width(), |
336
|
|
|
this._chartContainer.height(), |
337
|
|
|
false |
338
|
|
|
); |
339
|
|
|
} |
340
|
|
|
}.bind(this), |
341
|
|
|
stop: function(event, ui) { |
342
|
|
|
// set container height to auto after resizing (because of collapsing elements) |
343
|
|
|
this.container.css('height', 'auto'); |
344
|
|
|
|
345
|
|
|
}.bind(this) |
346
|
|
|
|
347
|
|
|
}); |
348
|
|
|
}, |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* Group: Evaluation |
352
|
|
|
*/ |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Method: _evaluateResult |
356
|
|
|
* Evaluates the job result. Either displays the analysis results or the returned error message. |
357
|
|
|
* |
358
|
|
|
* Parameters: |
359
|
|
|
* {String} data - Data returned from the backend containing global graph issues, |
360
|
|
|
* configuration data for Highcharts, as well as column definitions for DataTables. |
361
|
|
|
* |
362
|
|
|
* {String} job_result_url - URL for accessing the job result (configuration data). |
363
|
|
|
* |
364
|
|
|
*/ |
365
|
|
|
_evaluateResult: function(data, job_result_url) { |
366
|
|
|
|
367
|
|
|
data = jQuery.parseJSON(data); |
368
|
|
|
var issues = data.issues; |
369
|
|
|
var static_info = data.static_info; |
370
|
|
|
|
371
|
|
|
if (issues){ |
372
|
|
|
if (_.size(issues.errors) > 0 || _.size(issues.warnings) > 0){ |
373
|
|
|
this._displayGraphIssues(issues.errors, issues.warnings); |
374
|
|
|
} |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
if (static_info){ |
378
|
|
|
this._displayStaticInfo(static_info); |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
// remove progress bar |
382
|
|
|
this._chartContainer.empty(); |
383
|
|
|
var axisTitles = data.axis_titles; |
384
|
|
|
this._initializeHighcharts(axisTitles); |
385
|
|
|
|
386
|
|
|
|
387
|
|
|
// display results within a table |
388
|
|
|
var columns = data.columns; |
389
|
|
|
this._displayResultWithDataTables(columns, job_result_url); |
390
|
|
|
|
391
|
|
|
|
392
|
|
|
}, |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Method: _chartTooltipFormatter |
396
|
|
|
* This function is used to format the tooltip that appears when hovering over a data point in the chart. |
397
|
|
|
* The scope object ('this') contains the x and y value of the corresponding point. |
398
|
|
|
* |
399
|
|
|
* Returns: |
400
|
|
|
* A {String} that is displayed inside the tooltip. It may HTML. |
401
|
|
|
*/ |
402
|
|
|
_chartTooltipFormatter: function() { |
403
|
|
|
return '<b>' + this.series.name + '</b><br/>' + |
404
|
|
|
'<i>Probability:</i> <b>' + Highcharts.numberFormat(this.x, 5) + '</b><br/>' + |
405
|
|
|
'<i>Membership Value:</i> <b>' + Highcharts.numberFormat(this.y, 2) + '</b>'; |
406
|
|
|
}, |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Group: Conversion |
410
|
|
|
*/ |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* Method: _collectNodesAndEdgesForConfiguration |
414
|
|
|
* Traverses the graph and collects all nodes and edges which are part of the configuration defined by the |
415
|
|
|
* given set of choices. Remember those entities in the <_configNodeMap> and <_configEdgeMap> fields |
416
|
|
|
* using the given configID. |
417
|
|
|
* |
418
|
|
|
* Parameters: |
419
|
|
|
* {String} configID - The id of the configuration that is used to store the nodes and edges. |
420
|
|
|
* {Array[Object]} choices - A map from node IDs to choice objects (with 'type' and 'value') used to |
421
|
|
|
* filter the graph entities. |
422
|
|
|
* {<Node>} topNode - [optional] The top node of the graph. Used for recursion. Defaults to the |
423
|
|
|
* top event. |
424
|
|
|
* |
425
|
|
|
* Returns: |
426
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
427
|
|
|
*/ |
428
|
|
|
_collectNodesAndEdgesForConfiguration: function(configID, choices, topNode) { |
429
|
|
|
// start from top event if not further |
430
|
|
|
if (typeof topNode === 'undefined') topNode = this._editor.graph.getNodeById(0); |
431
|
|
|
// get children filtered by choice |
432
|
|
|
var children = topNode.getChildren(); |
433
|
|
|
var nodes = [topNode]; |
434
|
|
|
var edges = topNode.incomingEdges; |
435
|
|
|
|
436
|
|
|
if (topNode.id in choices) { |
437
|
|
|
var choice = choices[topNode.id]; |
438
|
|
|
|
439
|
|
|
switch (choice['type']) { |
440
|
|
|
case 'InclusionChoice': |
441
|
|
|
// if this node is not included (optional) ignore it and its children |
442
|
|
|
if (!choice['included']) { |
443
|
|
|
children = []; |
444
|
|
|
nodes = []; |
445
|
|
|
edges = []; |
446
|
|
|
} |
447
|
|
|
break; |
448
|
|
|
|
449
|
|
|
case 'FeatureChoice': |
450
|
|
|
// only pick the chosen child of a feature variation point |
451
|
|
|
children = [_.find(children, function(node) {return node.id == choice['featureId']})]; |
452
|
|
|
break; |
453
|
|
|
|
454
|
|
|
case 'RedundancyChoice': |
455
|
|
|
// do not highlight this node and its children if no child was chosen |
456
|
|
|
if (choice['n'] == 0) { |
457
|
|
|
nodes = []; |
458
|
|
|
children = []; |
459
|
|
|
edges = []; |
460
|
|
|
} |
461
|
|
|
break; |
462
|
|
|
} |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
this._configNodeMap[configID] = nodes.concat(this._configNodeMap[configID] || []); |
466
|
|
|
this._configEdgeMap[configID] = edges.concat(this._configEdgeMap[configID] || []); |
467
|
|
|
|
468
|
|
|
// recursion |
469
|
|
|
_.each(children, function(child) { |
470
|
|
|
this._collectNodesAndEdgesForConfiguration(configID, choices, child); |
471
|
|
|
}.bind(this)); |
472
|
|
|
|
473
|
|
|
return this; |
474
|
|
|
}, |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* Group: Display |
478
|
|
|
*/ |
479
|
|
|
|
480
|
|
|
/** |
481
|
|
|
* Method: _displayProgress |
482
|
|
|
* Display the job's progress in the menu's body. |
483
|
|
|
* |
484
|
|
|
* Parameters: |
485
|
|
|
* {Object} data - Data returned from the backend with information about the job's progress. |
486
|
|
|
* |
487
|
|
|
* Returns: |
488
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
489
|
|
|
*/ |
490
|
|
|
_displayProgress: function(data) { |
491
|
|
|
if (this._chartContainer.find('.progress').length > 0) return this; |
492
|
|
|
|
493
|
|
|
var progressBar = jQuery( |
494
|
|
|
'<div style="text-align: center;">' + |
495
|
|
|
'<p>' + this._progressMessage() + '</p>' + |
496
|
|
|
'<div class="progress progress-striped active">' + |
497
|
|
|
'<div class="progress-bar" role="progressbar" style="width: 100%;"></div>' + |
498
|
|
|
'</div>' + |
499
|
|
|
'</div>'); |
500
|
|
|
|
501
|
|
|
this._chartContainer.empty().append(progressBar); |
502
|
|
|
this._tableContainer.empty(); |
503
|
|
|
|
504
|
|
|
return this; |
505
|
|
|
}, |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* Method: _initializeHighcharts |
509
|
|
|
* Intialize highcharts with Axis definitions. |
510
|
|
|
* |
511
|
|
|
* Parameters: |
512
|
|
|
* {Array[Object]} axis_defititions - A set of definitions used in Highcharts initialisation. |
513
|
|
|
* |
514
|
|
|
* Returns: |
515
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
516
|
|
|
*/ |
517
|
|
|
_initializeHighcharts: function(axis_definitions) { |
518
|
|
|
var self = this; |
519
|
|
|
|
520
|
|
|
this._chart = new Highcharts.Chart({ |
521
|
|
|
chart: { |
522
|
|
|
renderTo: this._chartContainer[0], |
523
|
|
|
type: 'line', |
524
|
|
|
height: Factory.getModule('Config').AnalysisMenu.HIGHCHARTS_MIN_HEIGHT, |
525
|
|
|
|
526
|
|
|
}, |
527
|
|
|
title: { |
528
|
|
|
text: null |
529
|
|
|
}, |
530
|
|
|
credits: { |
531
|
|
|
style: { |
532
|
|
|
fontSize: Factory.getModule('Config').AnalysisMenu.HIGHCHARTS_CREDIT_LABEL_SIZE |
533
|
|
|
} |
534
|
|
|
}, |
535
|
|
|
xAxis: axis_definitions.xAxis, |
536
|
|
|
yAxis: axis_definitions.yAxis, |
537
|
|
|
tooltip: { |
538
|
|
|
formatter: this._chartTooltipFormatter |
539
|
|
|
}, |
540
|
|
|
plotOptions: { |
541
|
|
|
series: { |
542
|
|
|
marker: { |
543
|
|
|
radius: Factory.getModule('Config').AnalysisMenu.HIGHCHARTS_POINT_RADIUS |
544
|
|
|
}, |
545
|
|
|
events: { |
546
|
|
|
mouseOver : function () { |
547
|
|
|
var config_id = this.name |
548
|
|
|
var row = self._table.fnFindCellRowNodes(config_id, 0); |
549
|
|
|
jQuery(row).addClass('tr_hover'); |
550
|
|
|
}, |
551
|
|
|
mouseOut : function () { |
552
|
|
|
var config_id = this.name |
553
|
|
|
var row = self._table.fnFindCellRowNodes(config_id, 0); |
554
|
|
|
jQuery(row).removeClass('tr_hover'); |
555
|
|
|
}, |
556
|
|
|
} |
557
|
|
|
} |
558
|
|
|
}, |
559
|
|
|
data: [] |
560
|
|
|
}); |
561
|
|
|
|
562
|
|
|
return this; |
563
|
|
|
}, |
564
|
|
|
|
565
|
|
|
/** |
566
|
|
|
* Method: _displaySeriesWithHighcharts |
567
|
|
|
* Draw series within the Highcharts diagram. |
568
|
|
|
* |
569
|
|
|
* Parameters: |
570
|
|
|
* {Array[Object]} defititions - A set of one or more data series to display in the Highchart. |
571
|
|
|
* |
572
|
|
|
* Returns: |
573
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
574
|
|
|
*/ |
575
|
|
|
_displaySeriesWithHighcharts: function(data){ |
576
|
|
|
// remove series from the last draw |
577
|
|
|
while(this._chart.series.length > 0){ |
578
|
|
|
this._chart.series[0].remove(false); |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
var series = []; |
582
|
|
|
_.each(data, function(cutset, name) { |
583
|
|
|
series.push({ |
584
|
|
|
name: name, |
585
|
|
|
data: cutset |
586
|
|
|
}); |
587
|
|
|
}); |
588
|
|
|
|
589
|
|
|
// draw series |
590
|
|
|
_.each(series, function(config){ |
591
|
|
|
this._chart.addSeries(config, false, false) |
592
|
|
|
}.bind(this)); |
593
|
|
|
|
594
|
|
|
this._chart.redraw(); |
595
|
|
|
|
596
|
|
|
return this; |
597
|
|
|
}, |
598
|
|
|
|
599
|
|
|
/** |
600
|
|
|
* Method: _displayResultWithDataTables |
601
|
|
|
* Display the job's result with DataTables Plugin. Configuration Issues are printed inside the table as collapsed row. |
602
|
|
|
* |
603
|
|
|
* Parameters: |
604
|
|
|
* {Array[Object]} columns - A set of columns that shall be displayed within the table. |
605
|
|
|
* {String} job_result_url - URL under which the server delivers configurations for a specific analysis result (using ajax and pagingation) |
606
|
|
|
* |
607
|
|
|
* |
608
|
|
|
* Returns: |
609
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
610
|
|
|
*/ |
611
|
|
|
_displayResultWithDataTables: function(columns, job_result_url) { |
612
|
|
|
|
613
|
|
|
// clear container |
614
|
|
|
this._tableContainer.html('<table id="results_table" class="results_table table table-hover content"></table>'); |
615
|
|
|
|
616
|
|
|
|
617
|
|
|
var collapse_column = { |
618
|
|
|
"class": 'details-control', |
619
|
|
|
"orderable": false, |
620
|
|
|
"data": null, |
621
|
|
|
"defaultContent": '', |
622
|
|
|
"bSortable": false |
623
|
|
|
}; |
624
|
|
|
|
625
|
|
|
columns.push(collapse_column); |
626
|
|
|
|
627
|
|
|
//formating function for displaying configuration warnings/errors |
628
|
|
|
var format = function (issues) { |
629
|
|
|
|
630
|
|
|
var errors = issues['errors'] || []; |
631
|
|
|
var warnings = issues['warnings'] || []; |
632
|
|
|
|
633
|
|
|
return this._displayIsussuesList(errors, warnings); |
634
|
|
|
}.bind(this); |
635
|
|
|
|
636
|
|
|
this._table = jQuery('#results_table').dataTable({ |
637
|
|
|
"bProcessing": true, |
638
|
|
|
"bFilter": false, |
639
|
|
|
"bServerSide": true, |
640
|
|
|
"sAjaxSource": job_result_url, |
641
|
|
|
"aoColumns": columns, |
642
|
|
|
"bLengthChange": false, |
643
|
|
|
"iDisplayLength": Factory.getModule('Config').AnalysisMenu.RESULTS_TABLE_MAX_ROWS, |
644
|
|
|
"fnDrawCallback": function(oSettings) { |
645
|
|
|
|
646
|
|
|
var serverData = oSettings['json']; |
647
|
|
|
var totalRecords = serverData['iTotalRecords']; |
648
|
|
|
|
649
|
|
|
if(totalRecords < 2){ |
650
|
|
|
// unbind sorting events if there are less than 2 rows |
651
|
|
|
this._tableContainer.find('th').removeClass().unbind('click.DT'); |
652
|
|
|
} |
653
|
|
|
|
654
|
|
|
if(totalRecords <= Factory.getModule('Config').AnalysisMenu.RESULTS_TABLE_MAX_ROWS){ |
655
|
|
|
// remove pagination elements if only one page is displayed |
656
|
|
|
this._tableContainer.find('div.dataTables_paginate').css('display', 'none'); |
657
|
|
|
this._tableContainer.find('div.dataTables_info').css('display', 'none'); |
658
|
|
|
} |
659
|
|
|
|
660
|
|
|
// display points with highchart after table was rendered |
661
|
|
|
var configurations = serverData['aaData']; |
662
|
|
|
var chartData = {}; |
663
|
|
|
_.each(configurations, function(config) { |
664
|
|
|
var configID = config['id'] || ''; |
665
|
|
|
|
666
|
|
|
// collect chart data if given |
667
|
|
|
if (typeof config['points'] !== 'undefined') { |
668
|
|
|
chartData[configID] = _.sortBy(config['points'], function(point){ return point[0] }); |
669
|
|
|
} |
670
|
|
|
}.bind(this)); |
671
|
|
|
|
672
|
|
|
if (_.size(chartData) != 0) { |
673
|
|
|
this._displaySeriesWithHighcharts(chartData); |
674
|
|
|
} |
675
|
|
|
else{ |
676
|
|
|
this._chart.setSize (0, 0, false); |
677
|
|
|
} |
678
|
|
|
}.bind(this), |
679
|
|
|
"fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull) { |
680
|
|
|
|
681
|
|
|
// Callback is executed for each row (each row is one configuration) |
682
|
|
|
|
683
|
|
|
var current_config = aData; |
684
|
|
|
var configID = current_config['id']; |
685
|
|
|
|
686
|
|
|
if ('choices' in current_config ){ |
687
|
|
|
var choices = aData['choices']; |
688
|
|
|
|
689
|
|
|
// check if config meta data is alredy cached |
690
|
|
|
if (this._configMetaDataCached[configID] !== true){ |
691
|
|
|
// remember the nodes and edges involved in this config for later highlighting |
692
|
|
|
this._collectNodesAndEdgesForConfiguration(configID, choices); |
693
|
|
|
|
694
|
|
|
|
695
|
|
|
// remember the redundancy settings for this config for later highlighting |
696
|
|
|
this._redundancyNodeMap[configID] = {}; |
697
|
|
|
_.each(choices, function(choice, node) { |
698
|
|
|
if (choice.type == 'RedundancyChoice') { |
699
|
|
|
this._redundancyNodeMap[configID][node] = choice['n']; |
700
|
|
|
} |
701
|
|
|
}.bind(this)); |
702
|
|
|
|
703
|
|
|
this._configMetaDataCached[configID] = true; |
704
|
|
|
} |
705
|
|
|
|
706
|
|
|
jQuery(nRow).on("mouseover", function(){ |
707
|
|
|
this._highlightConfiguration(configID); |
708
|
|
|
}.bind(this)); |
709
|
|
|
|
710
|
|
|
|
711
|
|
|
jQuery(nRow).on("mouseleave", function(){ |
712
|
|
|
this._unhighlightConfiguration(); |
713
|
|
|
}.bind(this)); |
714
|
|
|
} |
715
|
|
|
|
716
|
|
|
/* Sample Configuration issues |
717
|
|
|
if (iDisplayIndex == 0){ |
718
|
|
|
current_config["issues"] = { "errors": [{"message": "map::at", "issueId": 0, "elementId": ""}]}; |
719
|
|
|
} else if (iDisplayIndex == 1){ |
720
|
|
|
current_config["issues"] = { "warnings": [{"message": "Ignoring invalid redundancy configuration with k=-2 N=0", "issueId": 0, "elementId": "3"}]}; |
721
|
|
|
} else if (iDisplayIndex == 2){ |
722
|
|
|
current_config["issues"] = { "errors": [{"message": "map::at", "issueId": 0, "elementId": ""},{"message": "error error error", "issueId": 0, "elementId": ""}, {"message": "another error", "issueId": 0, "elementId": ""}], "warnings": [{"message": "Ignoring invalid redundancy configuration with k=-2 N=0", "issueId": 0, "elementId": "3"}, {"message": "another warning", "issueId": 0, "elementId": "3"}] }; |
723
|
|
|
}*/ |
724
|
|
|
|
725
|
|
|
if ('issues' in current_config){ |
726
|
|
|
var issues = current_config['issues']; |
727
|
|
|
|
728
|
|
|
jQuery(nRow).find('td.details-control').append('<i class="fa fa-exclamation-triangle"></i>'); |
729
|
|
|
// Add event listener for opening and closing details |
730
|
|
|
jQuery(nRow).on('click', function () { |
731
|
|
|
var tr = jQuery(nRow); |
732
|
|
|
var row = this._table.api().row(tr); |
733
|
|
|
|
734
|
|
|
if ( row.child.isShown() ) { |
735
|
|
|
// This row is already open - close it |
736
|
|
|
row.child.hide(); |
737
|
|
|
} |
738
|
|
|
else { |
739
|
|
|
// Open this row |
740
|
|
|
row.child(format(issues)).show(); |
741
|
|
|
} |
742
|
|
|
}.bind(this)); |
743
|
|
|
|
744
|
|
|
if ('errors' in issues){ |
745
|
|
|
jQuery(nRow).addClass('danger'); |
746
|
|
|
} |
747
|
|
|
else if ('warnings' in issues){ |
748
|
|
|
jQuery(nRow).addClass('warning'); |
749
|
|
|
} |
750
|
|
|
} |
751
|
|
|
else{ |
752
|
|
|
// if row is not collapsable show default pointer |
753
|
|
|
jQuery(nRow).css('cursor', 'default'); |
754
|
|
|
} |
755
|
|
|
}.bind(this), |
756
|
|
|
|
757
|
|
|
"fnInitComplete": function(oSettings, json) { |
758
|
|
|
this._setupResizing(); |
759
|
|
|
// set minumum height of grid as the height of the first draw of the grid |
760
|
|
|
this._tableContainer.css('min-height', this._tableContainer.height()); |
761
|
|
|
// keep container width when switching the page (-> otherwise jumping width when switching) |
762
|
|
|
this.container.css('width', this.container.width()); |
763
|
|
|
|
764
|
|
|
}.bind(this) |
765
|
|
|
}); |
766
|
|
|
|
767
|
|
|
return this; |
768
|
|
|
}, |
769
|
|
|
|
770
|
|
|
/** |
771
|
|
|
* Method: _highlightConfiguration |
772
|
|
|
* Highlights all nodes, edges and n-values that are part of the given configuration on hover. |
773
|
|
|
* |
774
|
|
|
* Parameters: |
775
|
|
|
* {String} configID - The ID of the configuration that should be highlighted. |
776
|
|
|
* |
777
|
|
|
* Returns: |
778
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
779
|
|
|
*/ |
780
|
|
|
_highlightConfiguration: function(configID) { |
781
|
|
|
// prevents that node edge anchors are being displayed |
782
|
|
|
Canvas.container.addClass(Factory.getModule('Config').Classes.CANVAS_NOT_EDITABLE); |
783
|
|
|
|
784
|
|
|
// highlight nodes |
785
|
|
|
_.invoke(this._configNodeMap[configID], 'highlight'); |
786
|
|
|
// highlight edges |
787
|
|
|
_.invoke(this._configEdgeMap[configID], 'highlight'); |
788
|
|
|
// show redundancy values |
789
|
|
|
_.each(this._redundancyNodeMap[configID], function(value, nodeID) { |
790
|
|
|
var node = this._editor.graph.getNodeById(nodeID); |
791
|
|
|
node.showBadge('N=' + value, 'info'); |
792
|
|
|
}.bind(this)); |
793
|
|
|
|
794
|
|
|
return this; |
795
|
|
|
}, |
796
|
|
|
|
797
|
|
|
/** |
798
|
|
|
* Method: _unhighlightConfiguration |
799
|
|
|
* Remove all hover highlights handler currently attached. |
800
|
|
|
* |
801
|
|
|
* Returns: |
802
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
803
|
|
|
*/ |
804
|
|
|
_unhighlightConfiguration: function() { |
805
|
|
|
// make the anchors visible again |
806
|
|
|
Canvas.container.removeClass(Factory.getModule('Config').Classes.CANVAS_NOT_EDITABLE); |
807
|
|
|
|
808
|
|
|
// unhighlight all nodes |
809
|
|
|
_.invoke(this._editor.graph.getNodes(), 'unhighlight'); |
810
|
|
|
// unhighlight all edges |
811
|
|
|
_.invoke(this._editor.graph.getEdges(), 'unhighlight'); |
812
|
|
|
// remove all badges |
813
|
|
|
_.invoke(this._editor.graph.getNodes(), 'hideBadge'); |
814
|
|
|
|
815
|
|
|
return this; |
816
|
|
|
}, |
817
|
|
|
|
818
|
|
|
/** |
819
|
|
|
* Method: _displayStaticInfo |
820
|
|
|
Display unparsed static information from the backend |
821
|
|
|
* |
822
|
|
|
* Returns: |
823
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
824
|
|
|
*/ |
825
|
|
|
_displayStaticInfo: function(static_info) { |
826
|
|
|
this._staticInfoContainer.append(jQuery('<p>'+static_info+'</p>')); |
827
|
|
|
}, |
828
|
|
|
|
829
|
|
|
/** |
830
|
|
|
* Method: _displayGraphIssues |
831
|
|
|
* Display all warnings/errors that are thrown during graph validation. |
832
|
|
|
* |
833
|
|
|
* Parameters: |
834
|
|
|
* {Object} warnings - An array of warning objects. |
835
|
|
|
* {Object} errors - An array of error objects. |
836
|
|
|
* |
837
|
|
|
* Returns: |
838
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
839
|
|
|
*/ |
840
|
|
|
_displayGraphIssues: function(errors, warnings) { |
841
|
|
|
|
842
|
|
|
var num_errors = _.size(errors); |
843
|
|
|
var num_warnings = _.size(warnings); |
844
|
|
|
|
845
|
|
|
var alert_container = jQuery('<div class="alert"><i class="fa fa-exclamation-triangle"></i></div>') |
846
|
|
|
|
847
|
|
|
if (num_errors > 0){ |
848
|
|
|
alert_container.addClass('alert-error'); |
849
|
|
|
} else if(num_warnings >0){ |
850
|
|
|
alert_container.addClass('alert-warning'); |
851
|
|
|
} else{ |
852
|
|
|
alert_container.addClass('alert-success'); |
853
|
|
|
} |
854
|
|
|
|
855
|
|
|
var issues_heading = jQuery('<a class="alert-link">\ |
856
|
|
|
Errors: ' + num_errors + ' \ |
857
|
|
|
Warnings: ' + num_warnings + |
858
|
|
|
'</a>'); |
859
|
|
|
|
860
|
|
|
var issues_details = jQuery('<div class="collapse">' + this._displayIsussuesList(errors, warnings) + '</div>'); |
861
|
|
|
|
862
|
|
|
alert_container.append(issues_heading).append(issues_details); |
863
|
|
|
|
864
|
|
|
// collapse error/warning messages details after clicking on the heading |
865
|
|
|
issues_heading.click(function(){ |
866
|
|
|
issues_details.collapse('toggle'); |
867
|
|
|
}); |
868
|
|
|
|
869
|
|
|
this._graphIssuesContainer.append(alert_container); |
870
|
|
|
|
871
|
|
|
return this; |
872
|
|
|
}, |
873
|
|
|
|
874
|
|
|
|
875
|
|
|
/** |
876
|
|
|
* Method: _displayIsussuesList |
877
|
|
|
* Display all errors/warnings in a HTML unordered list. |
878
|
|
|
* |
879
|
|
|
* Parameters: |
880
|
|
|
* {Object} warnings - An array of warning objects. |
881
|
|
|
* {Object} errors - An array of error objects. |
882
|
|
|
* |
883
|
|
|
* Returns: |
884
|
|
|
* {String} contains HTML with error/warning messages. |
885
|
|
|
*/ |
886
|
|
|
_displayIsussuesList: function(errors, warnings){ |
887
|
|
|
var html_errors = ''; |
888
|
|
|
var html_warnings = ''; |
889
|
|
|
|
890
|
|
|
|
891
|
|
|
if(_.size(errors) > 0){ |
892
|
|
|
_.each(errors, function(error){ |
893
|
|
|
html_errors += '<li>' + error['message'] + '</li>'; |
894
|
|
|
}); |
895
|
|
|
html_errors = '<li><strong>Errors:</strong></li><ul>' + html_errors + '</ul>'; |
896
|
|
|
} |
897
|
|
|
|
898
|
|
|
if(_.size(warnings) > 0){ |
899
|
|
|
_.each(warnings, function(warning){ |
900
|
|
|
html_warnings += '<li>' + warning['message'] + '</li>'; |
901
|
|
|
}); |
902
|
|
|
html_warnings = '<li><strong>Warnings:</strong></li><ul>' + html_warnings + '</ul>'; |
903
|
|
|
} |
904
|
|
|
|
905
|
|
|
return '<ul>' + |
906
|
|
|
html_errors + |
907
|
|
|
html_warnings + |
908
|
|
|
'</ul>'; |
909
|
|
|
|
910
|
|
|
}, |
911
|
|
|
|
912
|
|
|
/** |
913
|
|
|
* Method: _displayJobError |
914
|
|
|
* Display an error massage resulting from a job error. |
915
|
|
|
* |
916
|
|
|
* Returns: |
917
|
|
|
* This {<AnalysisResultMenu>} for chaining. |
918
|
|
|
*/ |
919
|
|
|
_displayJobError: function(xhr) { |
920
|
|
|
Alerts.showErrorAlert( |
921
|
|
|
'An error occurred!', xhr.responseText || |
922
|
|
|
'We were trying to trigger a computational job, but this crashed on our side. A retry may help. The developers are already informed, sorry for the inconvinience.'); |
923
|
|
|
this.hide(); |
924
|
|
|
} |
925
|
|
|
}); |
926
|
|
|
|
927
|
|
|
|
928
|
|
|
/** |
929
|
|
|
* Class: AnalyticalProbabilityMenu |
930
|
|
|
* The menu responsible for displaying analysis results. |
931
|
|
|
* |
932
|
|
|
* Extends: {<AnalyticalResultMenu>} |
933
|
|
|
*/ |
934
|
|
|
var AnalyticalProbabilityMenu = AnalysisResultMenu.extend({ |
935
|
|
|
/** |
936
|
|
|
* Method: _containerID |
937
|
|
|
* Override of the abstract base class method. |
938
|
|
|
*/ |
939
|
|
|
_containerID: function() { |
940
|
|
|
return Factory.getModule('Config').IDs.ANALYTICAL_PROBABILITY_MENU; |
941
|
|
|
}, |
942
|
|
|
|
943
|
|
|
/** |
944
|
|
|
* Method: _progressMessage |
945
|
|
|
* Override of the abstract base class method. |
946
|
|
|
*/ |
947
|
|
|
_progressMessage: function() { |
948
|
|
|
return 'Running probability analysis...'; |
949
|
|
|
}, |
950
|
|
|
|
951
|
|
|
/** |
952
|
|
|
* Method: _menuHeader |
953
|
|
|
* Override of the abstract base class method. |
954
|
|
|
*/ |
955
|
|
|
_menuHeader: function() { |
956
|
|
|
return 'Analysis Results'; |
957
|
|
|
} |
958
|
|
|
}); |
959
|
|
|
|
960
|
|
|
|
961
|
|
|
/** |
962
|
|
|
* Class: SimulatedProbabilityMenu |
963
|
|
|
* The menu responsible for displaying simulation results. |
964
|
|
|
* |
965
|
|
|
* Extends: AnalysisResultMenu |
966
|
|
|
*/ |
967
|
|
|
var SimulatedProbabilityMenu = AnalysisResultMenu.extend({ |
968
|
|
|
/** |
969
|
|
|
* Method: _containerID |
970
|
|
|
* Override of the abstract base class method. |
971
|
|
|
*/ |
972
|
|
|
_containerID: function() { |
973
|
|
|
return Factory.getModule('Config').IDs.SIMULATED_PROBABILITY_MENU; |
974
|
|
|
}, |
975
|
|
|
|
976
|
|
|
/** |
977
|
|
|
* Method: _progressMessage |
978
|
|
|
* Override of the abstract base method. |
979
|
|
|
*/ |
980
|
|
|
_progressMessage: function() { |
981
|
|
|
return 'Running simulation...'; |
982
|
|
|
}, |
983
|
|
|
|
984
|
|
|
/** |
985
|
|
|
* Method: _menuHeader |
986
|
|
|
* Override of the abstract base class method. |
987
|
|
|
*/ |
988
|
|
|
_menuHeader: function() { |
989
|
|
|
return 'Simulation Results'; |
990
|
|
|
} |
991
|
|
|
}); |
992
|
|
|
|
993
|
|
|
return{ |
994
|
|
|
AnalyticalProbabilityMenu : AnalyticalProbabilityMenu, |
995
|
|
|
SimulatedProbabilityMenu : SimulatedProbabilityMenu, |
996
|
|
|
CutsetsMenu : CutsetsMenu |
997
|
|
|
} |
998
|
|
|
}); |
999
|
|
|
|