Free cookie consent management tool by TermsFeed Policy Generator

source: branches/OaaS/HeuristicLab.Services.Optimization.Web/Content/experiment.view.js @ 9922

Last change on this file since 9922 was 9508, checked in by fschoepp, 11 years ago

#1888:
HL:

  • Web projects requires different users to interact with hive. The singleton HiveServiceLocator.Instance doesn't allow different users at the same time, resulting in serialization during access of HiveClient methods.

The following changes have been introduced in favor of a parallel use of the HL libs:

  • HiveClient, TaskDownloader and ConcurrentTaskDownloader may now use a different IHiveServiceLocator than HiveServiceLocator.Instance (all methods have appropriate overloads now).
  • The default instance is still HiveServiceLocator.Instance.

Automated Scaling of Instances:

  • Added Scaler project to solution which represents a WorkerRole that scales the slave instances based on the global cpu utilization of all slaves.
  • Scaler is based on WASABi, rules can be adjusted in rulesstore.xml. Basic rule is: if < 45% global cpu utilization => remove an instance; if > 65% cpu => add an instance. Minimum boundary is 1 and maximum boundary is 8 slave instances.
  • Adjusted Slave project to automatically register itself to a SlaveGroup during WebRole startup (can be adjusted in service configuration).

Web-Frontend:

  • Added basic error messages to the dialogs when an ajax call fails.
  • Removed Styling.js from scripts.
File size: 52.9 KB
Line 
1var OAAS_VIEW = (function (my, Backbone, _, $, OAAS_MODEL) {
2    // ================ VIEWS =======================
3    my.ExperimentTreeView = Backbone.View.extend({
4        renderTree: function (model) {
5            var jsonModel = model.toJSON();
6            var self = this;
7            this.tree = $('<div />').dynatree({
8                children: [jsonModel],
9                autoExpandMS: 750,
10                onActivate: function (node) {
11                    // if we are able to remove this node from the tree, enable the remove button.
12                    if (node && node.parent.parent != null) {
13                        $('.remove-button', this.$el).removeAttr("disabled");
14                    } else if (node) {
15                        $('.remove-button', this.$el).attr("disabled", "disabled");
16                    }
17
18                    var modelNode = OAAS_MODEL.ExperimentNode.lookup(node.data.key);
19                    if (modelNode && !modelNode.get('isExperiment')) {
20                        $('.variate-button', this.$el).removeAttr("disabled");
21                    } else {
22                        $('.variate-button', this.$el).attr("disabled", "disabled");
23                    }
24                },
25                dnd: {
26                    onDragStart: function (node) {
27                        return node.parent.parent != null;
28                    },
29                    onDragEnter: function (node, sourceNode) {
30                        return true;
31                    },
32                    onDragOver: function (node, sourceNode, hitMode) {
33                        return node.data.title !== "Experiment" || hitMode === "over";
34                    },
35                    onDrop: function (node, sourceNode, hitMode, ui, draggable) {
36                        if (hitMode !== 'over')
37                            return;
38                        var modelNode = OAAS_MODEL.ExperimentNode.lookup(node.data.key);
39                        if (sourceNode) {
40                            var sourceModelNode = OAAS_MODEL.ExperimentNode.lookup(sourceNode.data.key);
41                            sourceNode.move(node, hitMode);
42                            // move node into the modelNode
43                            sourceModelNode.moveNode(modelNode);
44                        }
45                        else {
46                            var elem = $(draggable.element[0]);
47                            var newNode = { nodeId: elem.attr('id'), isExperiment: elem.attr('data-isExperiment') === 'true', title: elem.attr('data-name'), key: OAAS_MODEL.ExperimentNode.nextGlobalKey() };
48                            if (hitMode == "over") {
49                                node.addChild(newNode);
50                                node.expand(true);
51                                // update the data model
52                                newNode = modelNode.addNode(newNode);
53                                self.trigger('node-added', newNode);
54                            }
55                        }
56                        self.trigger('structure-changed', modelNode);
57                    }
58                }
59            });
60            this.tree.dynatree("getRoot").visit(function (node) {
61                node.expand(true);
62            });
63        },
64        initialize: function (spec) {
65            this.renderTree(spec.model);
66        },
67        events: {
68            'click .remove-button': 'doRemove',
69            'click .variate-button': 'doVariate'
70        },
71        render: function () {
72            this.$el.empty();
73            this.renderTree(this.model);
74            var content = $(_.template($('#stepwizard_template').html(), {}));
75            this.tree.appendTo(this.$el);
76            content.appendTo(this.$el);
77        },
78        doRemove: function () {
79            var node = this.tree.dynatree('getActiveNode');
80            if (node && node.parent.parent != null) {
81                var nxt = node.getNextSibling();
82                if (nxt == null)
83                    nxt = node.getPrevSibling();
84                if (nxt == null)
85                    nxt = node.getParent();
86                node.remove();
87                OAAS_MODEL.ExperimentNode.lookup(node.data.key).remove();
88                if (nxt) {
89                    this.tree.dynatree("getTree").activateKey(nxt.data.key);
90                }
91            }
92            this.trigger('structure-changed', node);
93        },
94        doVariate: function () {
95            var node = this.tree.dynatree('getActiveNode');
96            var modelNode = OAAS_MODEL.ExperimentNode.lookup(node.data.key);
97            this.trigger('variation-request', {
98                parentNode: node.parent,
99                model: modelNode
100            });
101        }
102    });
103
104    my.ExperimentDetailsTreeView = Backbone.View.extend({
105        render: function () {
106            var jsonModel = this.model.toJSON();
107            var self = this;
108            var tree = $('<div />').dynatree({
109                children: [jsonModel],
110                autoExpandMS: 750,
111                onActivate: function (node) {
112                    // if we click a node, simply open a dialog to enter it's parameters
113                    self.trigger('node-clicked', node);
114                }
115            });
116
117            this.$el.empty();
118            tree.appendTo(this.$el);
119            tree.dynatree("getRoot").visit(function (node) {
120                node.expand(true);
121            });
122        }
123    });
124
125    my.DraggableGroup = Backbone.View.extend({
126        initialize: function () {
127            this.collection.bind('remove', this.render, this);
128            this.collection.bind('add', this.render, this);
129        },
130        render: function () {
131            var self = this;
132            var div = $('<div />');
133            if (this.collection.length == 0) {
134                $('<p></p>').text("No items found!").appendTo(div);
135            }
136            for (var i = 0; i < this.collection.length; i++) {
137                new my.DraggableView({ model: this.collection.models[i], el: div }).render();
138            }
139
140            this.$el.empty();
141            div.appendTo(this.$el);
142        }
143    });
144
145
146    my.DraggableView = Backbone.View.extend({
147        render: function () {
148            var late = _.template($("#draggable_template").html(), this.model.attributes);
149            $(late).draggable({
150                revert: true,
151                connectToDynatree: true,
152                cursorAt: { top: -5, left: -5 },
153                helper: "clone"
154            }).appendTo(this.$el);
155        }
156    });
157
158    my.SelectableGroup = Backbone.View.extend({
159        initialize: function () {
160            this.collection.bind('remove', this.render, this);
161            this.collection.bind('add', this.render, this);
162            this.collection.bind('change', this.render, this);
163        },
164        render: function () {
165            var self = this;
166            var div = $('<div />');
167            if (this.collection.length == 0) {
168                $('<p></p>').text("No experiments found!").appendTo(div);
169            }
170
171            for (var i = 0; i < this.collection.length; i++) {
172                new my.SelectableView({ model: this.collection.models[i], el: div }).render();
173            }
174            this.$el.empty();
175            div.selectable({
176                filter: 'div',
177                selected: function (event, ui) {
178                    self.selected(event, ui);
179                }
180            }).appendTo(this.$el);
181        },
182        selected: function (event, ui) {
183            var nodeId = $(ui.selected).attr('id');
184            this.trigger('node-selected', nodeId);
185        }
186    });
187
188    my.SelectableView = Backbone.View.extend({
189        render: function () {
190            var late = _.template($("#draggable_template").html(), this.model.attributes);
191            $(late).appendTo(this.$el);
192        }
193    });
194
195    my.StepWizardView = Backbone.View.extend({
196        render: function () {
197            var self = this;
198            this.$el.smartWizard({
199                keyNavigation: false,
200                onLeaveStep: function (step) {
201                    var stepNum = parseInt(step.attr("rel"));
202                    return self.validateStep(stepNum);
203                },
204                onShowStep: function (step) {
205                    var stepNum = parseInt(step.attr("rel"));
206                    return self.showStep(stepNum);
207                },
208                onFinish: function () {
209                    self.trigger('experiment-finished');
210                }
211            });
212        },
213        validateStep: function (stepNumber) {
214            var validationModel = {
215                stepNumber: stepNumber,
216                succeeded: true
217            };
218            this.trigger('step-validated', validationModel);
219            return validationModel.succeeded;
220        },
221        showStep: function (stepNumber) {
222            this.trigger('step-shown', stepNumber);
223            return true;
224        }
225    });
226
227    my.ParameterWizard = Backbone.View.extend({
228        createWizard: function (steps) {
229            // use template instead of jquery?!
230            var newWizard = $('<div />').addClass('wizard swMain');
231            var stepMenu = $('<ul />');
232            var prefix = name + '_';
233            for (var i = 0; i < steps.length; i++) {
234                var step = steps[i];
235                var a = $('<a></a>', { href: '#' + prefix + (i + 1) });
236                $('<label />').addClass("stepNumber").text((i + 1)).appendTo(a);
237                $('<span />').addClass("stepDesc").html(step.heading + '<br /><small>' + step.description + '</small>').appendTo(a);
238                var li = $('<li />').append(a);
239                li.appendTo(stepMenu);
240            }
241            stepMenu.appendTo(newWizard);
242            for (var i = 0; i < steps.length; i++) {
243                var div = $('<div />', { id: prefix + (i + 1) });
244                if (steps[i].content && steps[i].content != null)
245                    div.append(steps[i].content);
246                div.appendTo(newWizard);
247            }
248            return newWizard;
249        },
250        render: function () {
251            var self = this;
252            this.$el.empty();
253            var data = this.model.get('data');
254
255            var hasProblemParameters = typeof data.ProblemParameters !== 'undefined';
256            var dialog = $('<div />');
257            var algoDiv = $('<div />');
258            var problemDiv = $('<div />');
259
260            for (var i = 0; i < data.AlgorithmParameters.length; i++) {
261                var parameterView = new my.ParameterEditableView({ model: data.AlgorithmParameters[i] });
262                // render to dialog
263                parameterView.render();
264                parameterView.$el.appendTo(algoDiv);
265            }
266
267            if (hasProblemParameters) {
268                var problemParameters = data.ProblemParameters;
269                for (var i = 0; i < problemParameters.length; i++) {
270                    var parameterView = new my.ParameterEditableView({ model: problemParameters[i] });
271                    // render to dialog
272                    parameterView.render();
273                    parameterView.$el.appendTo(problemDiv);
274                }
275            }
276
277            var newWizard =
278          hasProblemParameters ?
279            this.createWizard([
280              { heading: "Algorithm Parameters", description: "Adjust algorithm parameters", content: algoDiv },
281              { heading: "Problem Parameters", description: "Adjust problem parameters", content: problemDiv }
282            ]) :
283            this.createWizard([
284              { heading: "Algorithm Parameters", description: "Adjust algorithm parameters", content: algoDiv }
285            ]);
286
287            newWizard.appendTo(this.$el);
288            newWizard.smartWizard({
289                keyNavigation: false,
290                onFinish: function () {
291                    self.trigger('parameters-finished');
292                }
293            });
294        }
295    });
296
297    my.ParameterEditableView = Backbone.View.extend({
298        /*events: {
299        'change': 'valueChanged'
300        },*/
301        render: function () {
302            var mapper = new my.DatatypeMapper();
303            var content = $(_.template($('#parameter_template').html(), this.model));
304            mapper.mapHtml(this.model, $('.rightEntry', content));
305            content.appendTo(this.$el);
306        } /*,
307        valueChanged: function (param) {
308            var value = $(param.target).val();
309            if (!isNaN(value))
310                value = parseFloat(value);
311            var name = $(param.target).attr("name");
312            // normally a controller would do this, just for once, let the view update the model
313            var splitted = name.split("_");
314            if (splitted.length == 1) {
315                this.model.Value = value;
316            } else if (splitted.length == 2) {
317                this.model.Value[parseInt(splitted[1])] = value;
318            } else if (splitted.length == 3) {
319                this.model.Value[parseInt(splitted[1])][parseInt(splitted[2])] = value;
320            }
321        }*/
322    });
323
324
325    my.LoadingDialog = Backbone.View.extend({
326        initialize: function () {
327            this.reset();
328        },
329        render: function () {
330            var self = this;
331            this.$el.empty();
332            var dialog = $('<div />');
333            this.content.appendTo(dialog);
334            dialog.appendTo(this.$el);
335            this.$el.dialog({
336                autoOpen: true
337            });
338        },
339        text: function (txt) {
340            $('p', this.$el).text(txt);
341        },
342        setLoading: function (isLoading) {
343            if (isLoading)
344                $('img', this.$el).show();
345            else
346                $('img', this.$el).hide();
347        },
348        reset: function () {
349            this.content = $(_.template($('#loading_template').html(), {}));
350        },
351        setContent: function (el) {
352            this.content = el;
353        },
354        close: function () {
355            this.$el.dialog("close");
356        }
357    });
358
359
360    my.ParameterDialog = Backbone.View.extend({
361        render: function () {
362            var self = this;
363
364            this.$el.empty();
365            var data = this.model.get('data');
366
367            var hasProblemParameters = typeof data.ProblemParameters !== 'undefined';
368            var dialog = $('<div />');
369
370            var parameterWizard = new my.ParameterWizard({ model: this.model, el: dialog });
371            parameterWizard.render();
372            this.listenTo(parameterWizard, 'parameters-finished', function () {
373                self.trigger('parameters-finished');
374            });
375            dialog.appendTo(this.$el);
376            this.$el.dialog({
377                autoOpen: true,
378                width: 1024,
379                height: 768
380            });
381        },
382        close: function () {
383            this.$el.dialog("close");
384        }
385    });
386
387    my.ExperimentDetailsView = Backbone.View.extend({
388        events: {
389            'change': 'valueChanged'
390        },
391        valueChanged: function (param) {
392            var value = $(param.target).val();
393            if (!isNaN(value))
394                value = parseFloat(value);
395            var name = $(param.target).attr("name");
396            if (name == 'Name')
397                this.model.set({ title: value });
398            else if (name == 'RunImmediately')
399                this.model.set({ run: $(param.target).is(':checked') });
400            else if (name == 'Repititions')
401                this.model.set({ repititions: value });
402            else if (name == 'Group')
403                this.model.set({ group: value });
404
405            if (this.model.get('run')) {
406                $('input[name="Repititions"]', this.$el).removeAttr('disabled');
407                $('input[name="Group"]', this.$el).removeAttr('disabled');
408            } else {
409                $('input[name="Repititions"]', this.$el).attr('disabled', 'disabled');
410                $('input[name="Group"]', this.$el).attr('disabled', 'disabled');
411            }
412        },
413        render: function () {
414            $('input[name="Name"]', this.$el).val(this.model.get('title'));
415            $('input[name="RunImmediately"]', this.$el).attr('checked', this.model.get('run'));
416            $('input[name="Repititions"]', this.$el).val(this.model.get('repititions'));
417            $('input[name="Group"]', this.$el).val(this.model.get('group'));
418        }
419    });
420
421    my.ValidationHintsView = Backbone.View.extend({
422        render: function () {
423            var template = _.template($('#validationhints_template').html(), this.model);
424            $(template).dialog({
425                resizable: false,
426                modal: true,
427                buttons: {
428                    "OK": function () {
429                        $(this).dialog("close");
430                    }
431                }
432            });
433        }
434    });
435
436    // ========== Variation Dialog Views ============
437
438    my.VariationDialog = Backbone.View.extend({
439        initialize: function (spec) {
440
441        },
442        events: {
443            'change': 'entrySelected'
444        },
445        render: function () {
446            var self = this;
447            var dialog = $(_.template($('#variationdialog_template').html(), {}));
448
449            var content = $('select[class="variationDialogContent"]', dialog);
450            var data = this.model.get('data');
451            for (var i = 0; i < data.AlgorithmParameters.length; i++) {
452                data.AlgorithmParameters[i].Index = i;
453                var entry = new my.VariationEntryView({ model: data.AlgorithmParameters[i], el: content });
454                entry.render();
455            }
456
457            this.details = $('div[class="variationDetails"]', dialog);
458            this.activateButton = $('input[name="active"]', dialog);
459            this.activateButton.change(function (evt) {
460                var checked = $(evt.target).is(':checked');
461                if (self.selectedModel && self.selectedModel != null) {
462                    self.selectedModel.Active = checked;
463                }
464
465                if (self.selectedModel.Active) {
466                    $('*', self.details).removeAttr('disabled');
467                }
468                else {
469                    //$('input,button,select', self.details).attr('disabled', 'disabled');
470                    $('*', self.details).attr('disabled', 'disabled');
471                }
472            });
473            dialog.dialog({
474                height: 300,
475                buttons: {
476                    "Abort": function () {
477                        $(this).dialog("close");
478                    },
479                    "OK": function () {
480                        self.generateVariations();
481                        $(this).dialog("close");
482                    }
483                }
484            });
485
486            content.change(function (evt) {
487                var optionSelected = $("option:selected", this);
488                var model = self.model.get('data').AlgorithmParameters[parseInt($(optionSelected).attr('data-index'))]
489                self.entrySelected(model);
490            });
491
492            this.variationDetails = new my.VariationContentView({ el: this.details });
493            var model = this.model.get('data').AlgorithmParameters[0];
494            this.entrySelected(model);
495        },
496        entrySelected: function (model) {
497            this.selectedModel = model;
498            this.variationDetails.model = model;
499            this.details.empty();
500            this.variationDetails.render();
501            if (model.Active) {
502                this.activateButton.attr('checked', 'checked');
503                $('*', this.details).removeAttr('disabled');
504            }
505            else {
506                this.activateButton.removeAttr('checked');
507                $('*', this.details).attr('disabled', 'disabled');
508            }
509        },
510        generateVariations: function () {
511            this.solutions = [];
512            this.exhaust(0, []);
513            this.trigger('variations-generated', this.solutions);
514        },
515        exhaust: function (index, element) {
516            if (index == this.model.get('data').AlgorithmParameters.length) {
517                // we found a solution! store it by creating a deep copy
518                this.solutions.push($.extend(true, [], element));
519                return;
520            }
521
522            var currentParameter = this.model.get('data').AlgorithmParameters[index];
523            if (currentParameter.Active && currentParameter.Generated) {
524                for (var i = 0; i < currentParameter.Generated.length; i++) {
525                    element[index] = {
526                        Name: currentParameter.Name,
527                        Value: currentParameter.Generated[i]
528                    };
529                    if (currentParameter.Options) {
530                        element[index].Options = currentParameter.Options;
531                    }
532                    this.exhaust(index + 1, element);
533                }
534            } else {
535                element[index] = {
536                    Name: currentParameter.Name,
537                    Value: currentParameter.Value
538                };
539                if (currentParameter.Options) {
540                    element[index].Options = currentParameter.Options;
541                }
542                this.exhaust(index + 1, element);
543            }
544        }
545    });
546
547    my.TableEditView = Backbone.View.extend({
548        events: {
549            'change': 'valueChanged',
550            'click button[data-operation="Add"]': 'addClicked',
551            'click button[data-operation="Remove"]': 'removeClicked'
552        },
553        render: function () {
554            this.$el.empty();
555            // http: //stackoverflow.com/questions/8749236/create-table-with-jquery-append
556            var table = $('<table></table>').addClass('editableTable');
557
558            // determine dimensions
559            var rows = this.model.length;
560            var columns = 1;
561            if ($.isArray(this.model[0])) {
562                columns = this.model[0].length;
563            }
564
565            // create head elements
566            var head = $('<thead></thead>');
567            var headerRow = $('<tr></tr>');
568            headerRow.appendTo(head);
569            if (this.options.rowNames) {
570                var rowNames = this.options.rowNames;
571                for (var i = 0; i < rowNames.length; i++) {
572                    $('<td />').text(rowNames[i]).appendTo(headerRow);
573                }
574            } else {
575                for (var i = 0; i < columns; i++) {
576                    $('<td />').text((i + 1) + '. Column').appendTo(headerRow);
577                }
578            }
579            head.appendTo(table);
580
581            // create body
582            var body = $('<tbody></tbody>');
583
584            for (var i = 0; i < rows; i++) {
585                var row = $('<tr></tr>');
586                for (var j = 0; j < columns; j++) {
587                    var rowContent = this.model[i];
588                    var columnEntry = $.isArray(rowContent) ? rowContent[j] : rowContent;
589                    var col = $('<td></td>');
590                    if (this.options.editable) {
591                        $('<input type="text" name="' + i + '_' + j + '" />').val(columnEntry).appendTo(col);
592                    } else {
593                        col.text(columnEntry);
594                    }
595                    col.appendTo(row);
596                }
597                if (this.options.editable) {
598                    $('<button data-operation="Add" name="' + i + '" />').val(columnEntry).text('+').appendTo(col);
599                    $('<button data-operation="Remove" name="' + i + '" />').val(columnEntry).text('-').appendTo(col);
600                }
601                row.appendTo(body);
602            }
603            body.appendTo(table);
604
605            table.appendTo(this.$el);
606            if (this.options.useDatatable)
607                table.dataTable();
608        },
609        valueChanged: function (evt) {
610            var target = $(evt.target);
611            var value = target.val();
612            var index = target.attr('name');
613            var splittedName = index.split('_');
614            var i = parseInt(splittedName[0]);
615            var j = parseInt(splittedName[1]);
616            if ($.isArray(this.model[i])) {
617                this.model[i][j] = parseFloat(value);
618            } else {
619                this.model[i] = parseFloat(value);
620            }
621        },
622        addClicked: function (evt) {
623            var target = $(evt.target);
624            var index = target.attr('name');
625            var i = parseInt(index);
626            if ($.isArray(this.model[i])) {
627                var cpy = [];
628                for (var j = 0; j < this.model[i].length; j++) {
629                    cpy.push(this.model[i][j]);
630                }
631                this.model.splice(i, 0, cpy);
632            } else {
633                this.model.splice(i, 0, this.model[i]);
634            }
635            // render after model changes
636            this.render();
637        },
638        removeClicked: function (evt) {
639            var target = $(evt.target);
640            var index = target.attr('name');
641            var i = parseInt(index);
642            this.model.splice(i, 1);
643            // render after model changes
644            this.render();
645        }
646    });
647
648    my.NumberEditableView = Backbone.View.extend({
649        events: {
650            'change': 'valueChanged'
651        },
652        render: function () {
653            $('<input type="text" />').val(this.model.Value).appendTo(this.$el);
654
655        },
656        valueChanged: function (evt) {
657            var value = $(evt.target).val();
658            if (!isNaN(value)) {
659                value = parseFloat(value);
660                this.model.Value = value;
661            } else {
662                $('input', this.$el).val(this.model.Value);
663            }
664        }
665    });
666
667    my.SelectionView = Backbone.View.extend({
668        events: {
669            'change': 'checked'
670        },
671        render: function () {
672            this.$el.empty();
673            var s = $('<select />').attr('data-name', this.model.Name).appendTo(this.$el);
674            for (var i = 0; i < this.model.Options.length; i++) {
675                $('<option />', { value: this.model.Options[i], text: this.model.Options[i] }).appendTo(s);
676            }
677            s.val(this.model.Value);
678        },
679        checked: function (evt) {
680            var value = $(evt.target).val();
681            var name = $(evt.target).attr('data-name')
682            this.trigger('selected', { name: name, value: value });
683        }
684    });
685
686    my.OptionSelectionView = Backbone.View.extend({
687        events: {
688            'change': 'checked'
689        },
690        render: function () {
691            this.$el.empty();
692            if ($.isArray(this.model)) {
693                for (var i = 0; i < this.model.length; i++) {
694                    $(_.template($('#checkbox_template').html(), { checked: false, name: this.model[i], text: this.model[i], hideText: this.options.hideText })).appendTo(this.$el);
695                }
696            }
697            else {
698                $(_.template($('#checkbox_template').html(), { checked: this.model, name: this.model, text: this.model, hideText: this.options.hideText })).appendTo(this.$el);
699            }
700        },
701        checked: function (evt) {
702            var checked = $(evt.target).is(':checked');
703            var name = $(evt.target).attr('name')
704            this.trigger('changed', { name: name, checked: checked });
705        }
706    });
707
708    my.VariationContentView = Backbone.View.extend({
709        render: function () {
710            var self = this;
711            if (this.model.Value === 'false' || this.model.Value === 'true' ||
712              this.model.Value === true || this.model.Value === false) {
713                $(_.template($('#variation_boolean_template').html(), {})).appendTo(this.$el);
714                this.model.Generated = [false, true];
715            }
716            else if (!isNaN(this.model.Value)) {
717                var elem = $(_.template($('#variation_number_template').html(), {}));
718                elem.appendTo(this.$el)
719                $('input[name="generate"]', elem).click(function (evt) { self.openGenerator(evt); });
720                if (!this.model.Generated) {
721                    this.model.Generated = [0, 1, 2];
722                }
723                var tev = new my.TableEditView({ model: this.model.Generated, el: $('div div', this.$el), editable: true });
724                tev.render();
725            }
726            else if (this.model.Options) {
727                var osv = new my.OptionSelectionView({ model: this.model.Options, el: this.$el });
728                this.listenTo(osv, 'changed', function (evt) {
729                    self.selectionChanged(evt);
730                });
731                if (!this.model.Generated)
732                    this.model.Generated = [];
733                osv.render();
734                for (var i = 0; i < this.model.Generated.length; i++) {
735                    $('input[name="' + this.model.Generated[i] + '"]', this.$el).attr('checked', 'checked');
736                }
737            }
738        },
739        openGenerator: function (evt) {
740            var self = this;
741            var generator = new my.GeneratorDialog();
742            this.listenTo(generator, 'success', function (data) {
743                self.doGenerate(data);
744            });
745            generator.render();
746        },
747        doGenerate: function (data) {
748            if (data.minimum > data.maximum) {
749                var tmp = data.minimum;
750                data.minimum = data.maximum;
751                data.maximum = tmp;
752            }
753            if (data.step < 0)
754                data.step *= -1;
755            if (data.step == 0)
756                data.step = 1;
757            this.model.Generated = _.range(data.minimum, data.maximum + 1, data.step);
758            var tev = new my.TableEditView({ model: this.model.Generated, el: $('div div', this.$el), editable: true });
759            tev.render();
760        },
761        selectionChanged: function (evt) {
762            if (!evt.checked) {
763                this.model.Generated = _.without(this.model.Generated, evt.name);
764            } else if (_.indexOf(this.model.Generated, evt.name) == -1) {
765                this.model.Generated.push(evt.name);
766            }
767        }
768    });
769
770    my.GeneratorDialog = Backbone.View.extend({
771        render: function () {
772            var self = this;
773            var generator = $(_.template($('#variation_generator_template').html(), {}));
774            generator.dialog({
775                buttons: {
776                    "Abort": function () {
777                        $(this).dialog("close");
778                    },
779                    "OK": function () {
780                        $(this).dialog("close");
781                        self.trigger("success", {
782                            minimum: parseInt($('input[name="minimum"]', generator).val()),
783                            maximum: parseInt($('input[name="maximum"]', generator).val()),
784                            step: parseFloat($('input[name="step"]', generator).val())
785                        });
786                    }
787                }
788            });
789        }
790    });
791
792    my.VariationEntryView = Backbone.View.extend({
793        render: function () {
794            var entryTemplate = _.template($('#variationentry_template').html(), this.model);
795            $(entryTemplate).appendTo(this.$el);
796        }
797    });
798
799    // ========================== Run Collection Views ===================================
800
801    function percentile(p, data) {
802        var i = (data.length * p) / 100 + 0.5;
803        var f = i - parseInt(i);
804        var k = Math.floor(i) - 1;
805        return (1 - f) * data[k] + f * data[k + 1];
806    }
807
808    my.RunCollectionView = Backbone.View.extend({
809        events: {
810            'change .x': 'xAxisSelected',
811            'change .y': 'yAxisSelected',
812            'change .plotType': 'plotSelected',
813            'change .datatableOptions': 'datatableSelected',
814            'change .datatableRow': 'datatableRowSelected',
815            'change .bubbleSize': 'bubbleTypeSelected'
816        },
817        render: function () {
818            if (this.model.models.length == 0)
819                return;
820
821            var self = this;
822            var ele = $(_.template($('#runcollection_template').html(), {}));
823            var xAxisSelect = $('.x', ele);
824            var yAxisSelect = $('.y', ele);
825            var results = this.model.models[0].get('results');
826            this.options.datatables = {};
827            var datatableSelect = $('.datatableOptions', ele);
828
829            var names = [];
830            var previouslyFound = { results: {}, params: {} };
831            _.each(this.model.models, function (model) {
832                _.each(model.get('results'), function (res) {
833                    if (!previouslyFound['results'][res.Name]) {
834                        previouslyFound['results'][res.Name] = true;
835                        names.push({ name: res.Name, source: 'results' });
836                    }
837                });
838                _.each(model.get('params'), function (res) {
839                    if (!previouslyFound['params'][res.Name]) {
840                        previouslyFound['params'][res.Name] = true;
841                        names.push({ name: res.Name, source: 'params' });
842                    }
843                });
844            });
845            previouslyFound = {};
846            this.options.modelMapping = names;
847            this.bubbleSelect = $('select[class="bubbleSize"]', ele);
848
849            _.each(names, function (entry) {
850                var option = $("<option />", { value: entry.name, text: entry.name });
851                option.clone().appendTo(xAxisSelect);
852                option.clone().appendTo(yAxisSelect);
853                option.clone().appendTo(self.bubbleSelect);
854                var rowNames = null;
855                var foundModel = _.find(self.model.models, function (model) {
856                    var e = _.find(model.get(entry.source), function (e) { return e.Name == entry.name && e.RowNames });
857                    if (e)
858                        rowNames = e.RowNames;
859                    return e && e.RowNames;
860                });
861                if (rowNames) {
862                    self.options.datatables[entry.name] = rowNames;
863                    $('<option />', { value: entry.name, text: entry.name }).appendTo(datatableSelect);
864                }
865            });
866
867            // prepare slider settings
868            this.options.bubbleSize = 10;
869            this.options.xAxis = this.model.models[0].get('results')[0].Name;
870            this.options.yAxis = this.options.xAxis;
871            this.options.selectedXIndex = 0;
872            this.options.selectedYIndex = 0;
873            this.options.plot = 'Boxplot';
874            $('div[class="bubbleSlider"]', ele).slider({
875                range: "max",
876                min: 10,
877                max: 40,
878                value: 10,
879                stop: function (event, ui) {
880                    self.options.bubbleSize = ui.value;
881                    self.createPlot();
882                }
883            }).css('width', '120px').css('margin-left', '15px').css('margin-right', '15px').css('display', 'inline-block');
884            ele.appendTo(this.$el);
885
886            // init resizer
887            this.plotWidth = 500;
888            this.plotHeight = 500;
889            var parent = $('div[class="resizer"]', this.$el);
890            this.resizerView = new my.ResizableView({ el: parent });
891            this.listenTo(this.resizerView, 'resized', function (evt) {
892                self.updatePlot(
893         $(evt.target).width() * 0.96,
894         $(evt.target).height() * 0.96
895        );
896            });
897            this.resizerView.render();
898            this.updateRowSelect(datatableSelect.val());
899            this.createPlot();
900            this.updatePlot(
901         600,
902         500
903        );
904        },
905        updateRowSelect: function (name) {
906            if (!name)
907                return;
908            var rowSelect = $('select[class="datatableRow"]', this.$el);
909            rowSelect.empty();
910            var rownames = this.options.datatables[name];
911            for (var i = 0; i < rownames.length; i++) {
912                $('<option />', { value: rownames[i], text: rownames[i] }).appendTo(rowSelect);
913            }
914            this.options.selectedDatatable = name;
915            this.options.datatableRow = rowSelect.val();
916            this.createPlot();
917        },
918        datatableSelected: function (evt) {
919            var value = $(evt.target).val();
920            this.options.selectedDatatable = value;
921            this.updateRowSelect(value);
922        },
923        datatableRowSelected: function (evt) {
924            var value = $(evt.target).val();
925            this.options.datatableRow = value;
926            this.createPlot();
927        },
928        xAxisSelected: function (evt) {
929            var target = $(evt.target);
930            this.options.selectedXIndex = target.prop("selectedIndex");
931            this.options.xAxis = target.val();
932            this.createPlot();
933        },
934        yAxisSelected: function (evt) {
935            var target = $(evt.target);
936            this.options.selectedYIndex = target.prop("selectedIndex");
937            this.options.yAxis = target.val();
938            this.createPlot();
939        },
940        plotSelected: function (evt) {
941            var target = $(evt.target);
942            this.options.plot = target.val();
943            this.createPlot();
944        },
945        bubbleTypeSelected: function (evt) {
946            this.createPlot();
947        },
948        updateVisibility: function (evt) {
949            if (this.options.plot == 'Boxplot') {
950                $('span[class="choice"]', this.$el).show();
951                $('span[class="bubble"]', this.$el).hide();
952                $('span[class="datatable"]', this.$el).hide();
953            } else if (this.options.plot == 'Bubblechart') {
954                $('span[class="choice"]', this.$el).show();
955                $('span[class="bubble"]', this.$el).show();
956                $('span[class="datatable"]', this.$el).hide();
957            } else if (this.options.plot == 'Datatable') {
958                $('span[class="choice"]', this.$el).hide();
959                $('span[class="bubble"]', this.$el).hide();
960                $('span[class="datatable"]', this.$el).show();
961            }
962        },
963        updatePlot: function (width, height) {
964            this.plotDiv.height(height);
965            this.plotDiv.width(width);
966            this.plotWidth = width;
967            this.plotHeight = height;
968            this.plot.refresh();
969        },
970        createPlot: function () {
971            if (this.options.plot && this.options.xAxis && this.options.yAxis) {
972                if (this.options.plot == 'Boxplot') {
973                    this.createBoxplot();
974                } else if (this.options.plot == 'Bubblechart') {
975                    this.createBubblechart();
976                } else if (this.options.plot == 'Datatable') {
977                    this.createDatatable();
978                }
979                this.updateVisibility();
980            }
981        },
982        preparePlotDiv: function () {
983            var parent = $('div[class="plot"]', this.$el);
984            parent.empty();
985            this.plotDiv = $('<div></div>').css('width', this.plotWidth).css('height', this.plotHeight).appendTo(parent);
986            return this.plotDiv;
987        },
988        createBoxplot: function () {
989            var self = this;
990            var parameterXSource = this.options.modelMapping[this.options.selectedXIndex].source; // either 'params' or 'results'
991            var parameterYSource = this.options.modelMapping[this.options.selectedYIndex].source; // either 'params' or 'results'
992            // prepare data for boxplot
993            var values = {};
994            for (var i = 0; i < this.model.models.length; i++) {
995                var xlabel = _.find(this.model.models[i].get(parameterXSource), function (itm) { return itm.Name == self.options.xAxis });
996                if (!xlabel)
997                    continue;
998
999                if ($.isArray(xlabel.Value))
1000                    values[parameterXSource + '.' + xlabel.Name + ' = ' + xlabel.Value[0]] = [];
1001                else
1002                    values[parameterXSource + '.' + xlabel.Name + ' = ' + xlabel.Value] = [];
1003            }
1004
1005            for (var i = 0; i < this.model.models.length; i++) {
1006                var xlabel = _.find(this.model.models[i].get(parameterXSource), function (itm) { return itm.Name == self.options.xAxis });
1007                var entry = _.find(this.model.models[i].get(parameterYSource), function (itm) { return itm.Name == self.options.yAxis });
1008                if (!xlabel || !entry || !entry.Value)
1009                    continue;
1010
1011                var index = parameterXSource + '.' + xlabel.Name + ' = ' + ($.isArray(xlabel.Value) ? xlabel.Value[0] : xlabel.Value);
1012
1013                if ($.isArray(entry.Value))
1014                    values[index].push(entry.Value[0]);
1015                else
1016                    values[index].push(entry.Value);
1017            }
1018
1019            // create boxplot
1020            var div = this.preparePlotDiv();
1021            var boxPlot = new my.BoxplotView({ model: values, el: div });
1022            this.plot = boxPlot;
1023            boxPlot.render();
1024        },
1025        createBubblechart: function () {
1026            var self = this;
1027            var parameterXSource = this.options.modelMapping[this.options.selectedXIndex].source; // either 'params' or 'results'
1028            var parameterYSource = this.options.modelMapping[this.options.selectedYIndex].source; // either 'params' or 'results'
1029            // prepare data for bubble chart
1030            var values = [];
1031            var autoscaleBubbles = this.bubbleSelect.val() != 'Constant';
1032            for (var i = 0; i < this.model.models.length; i++) {
1033                var xValue = _.find(this.model.models[i].get(parameterXSource), function (itm) { return itm.Name == self.options.xAxis });
1034                var yValue = _.find(this.model.models[i].get(parameterYSource), function (itm) { return itm.Name == self.options.yAxis });
1035                if (!xValue || !yValue)
1036                    continue;
1037
1038                // determine size
1039                var size = 0;
1040                if (this.bubbleSelect.val() == 'Constant') {
1041                    size = this.options.bubbleSize;
1042                } else {
1043                    var valueProvider = _.find(this.model.models[i].get('results'), function (itm) { return itm.Name == self.bubbleSelect.val(); });
1044                    if (!valueProvider)
1045                        valueProvider = _.find(this.model.models[i].get('params'), function (itm) { return itm.Name == self.bubbleSelect.val(); });
1046
1047                    if (!valueProvider) {
1048                        size = this.options.bubbleSize;
1049                    } else {
1050                        var value = valueProvider.Value;
1051                        while ($.isArray(value)) {
1052                            value = value[0];
1053                        }
1054                        size = parseInt(value.replace(/,/, '.'));
1055                        if (isNaN(size))
1056                            size = this.options.bubbleSize;
1057                    }
1058                }
1059
1060                // add element
1061                var xFloat = parseFloat(xValue.Value.replace(/,/, '.'));
1062                var yFloat = parseFloat(yValue.Value.replace(/,/, '.'));
1063
1064                values.push([xFloat, yFloat, size, 'Run' + (i + 1)]);
1065            }
1066
1067            // render bubble chart
1068            var div = this.preparePlotDiv();
1069            var bubblePlot = new my.BubbleplotView({ model: values, el: div, autoscale: autoscaleBubbles });
1070            this.plot = bubblePlot;
1071            bubblePlot.render();
1072        },
1073        createDatatable: function () {
1074            if (!this.options.selectedDatatable)
1075                return;
1076            var self = this;
1077
1078            // prepare data for datatable
1079            var values = [];
1080            var rowNames = [];
1081            for (var i = 0; i < this.model.models.length; i++) {
1082                var table = _.find(this.model.models[i].get('results'), function (itm) { return itm.Name == self.options.selectedDatatable });
1083                if (!table)
1084                    table = _.find(this.model.models[i].get('params'), function (itm) { return itm.Name == self.options.selectedDatatable });
1085
1086                if (!table)
1087                    continue;
1088                var index = table.RowNames.indexOf(this.options.datatableRow);
1089                if (index == -1)
1090                    continue;
1091                var entries = [];
1092                for (var j = 0; j < table.Value.length; j++) {
1093                    entries.push(table.Value[j][index]);
1094                }
1095                values.push(entries);
1096                rowNames.push(this.options.datatableRow + ' of Run Number ' + (i + 1));
1097            }
1098
1099            // render datatable
1100            var div = this.preparePlotDiv();
1101            var plot = new my.PlotView({ model: values, el: div, rownames: rowNames });
1102            this.plot = plot;
1103            plot.render();
1104        }
1105    });
1106
1107    // ===================== Plots =============================
1108
1109    my.PlotView = Backbone.View.extend({
1110        render: function () {
1111            this.$el.empty();
1112            var plotLabels = undefined;
1113            var labelSource = this.model.RowNames ? this.model.RowNames : this.options.rownames;
1114            if (labelSource) {
1115                plotLabels = [];
1116                for (var i = 0; i < labelSource.length; i++) {
1117                    plotLabels.push({ label: labelSource[i] });
1118                }
1119            }
1120
1121            var values = this.model.Value ? this.model.Value : this.model;
1122
1123            var plotValues = [];
1124
1125            if (!this.options.transpose) {
1126                if ($.isArray(values[0])) {
1127                    for (var i = 0; i < values.length; i++) {
1128                        plotValues.push(values[i]);
1129                    }
1130                } else {
1131                    plotValues.push(values);
1132                }
1133            }
1134            else {
1135                if ($.isArray(values[0])) {
1136                    var columnCount = values[0].length;
1137                    var colVals = {};
1138                    for (var i = 0; i < columnCount; i++)
1139                        colVals[i] = [];
1140
1141                    for (var i = 0; i < values.length; i++) {
1142                        for (var j = 0; j < columnCount; j++) {
1143                            colVals[j].push(values[i][j]);
1144                        }
1145                    }
1146
1147                    for (var i = 0; i < columnCount; i++) {
1148                        plotValues.push(colVals[i]);
1149                    }
1150                } else {
1151                    plotValues.push(values);
1152                }
1153            }
1154
1155            if (!this.$el.attr('id')) {
1156                this.$el.attr('id', my.PlotView.nextId());
1157            }
1158
1159            if (!this.$el.css('width'))
1160                this.$el.css('width', '500px');
1161
1162            this.plot = $.jqplot(this.$el.attr('id'), plotValues, {
1163                cursor: {
1164                    show: true,
1165                    zoom: true,
1166                    showTooltip: false
1167                },
1168                series: plotLabels,
1169                seriesDefaults: {
1170                    lineWidth: 1.5,
1171                    markerOptions: {
1172                        size: 2,
1173                        lineWidth: 2
1174                    }
1175                },
1176                legend: {
1177                    show: true,
1178                    placement: 'outsideGrid'
1179                },
1180                highlighter: {
1181                    show: true,
1182                    sizeAdjust: 7.5
1183                }
1184            });
1185        },
1186        refresh: function () {
1187            if (this.plot)
1188                this.plot.replot({ resetAxes: true });
1189        }
1190    },
1191  {
1192      plotId: 1,
1193      nextId: function () {
1194          return my.PlotView.plotId++;
1195      }
1196  });
1197
1198    my.BubbleplotView = Backbone.View.extend({
1199        render: function () {
1200            this.$el.empty();
1201
1202            var values = this.model && this.model.Value ? this.model.Value : this.model;
1203
1204            var plotValues = [];
1205
1206
1207            if (!this.$el.attr('id')) {
1208                this.$el.attr('id', my.PlotView.nextId());
1209            }
1210
1211            if (!this.$el.css('width'))
1212                this.$el.css('width', '500px');
1213
1214            this.plot = $.jqplot(this.$el.attr('id'), [values], {
1215                cursor: {
1216                    show: true,
1217                    zoom: true,
1218                    showTooltip: false
1219                },
1220                seriesDefaults: { renderer: $.jqplot.BubbleRenderer,
1221                    rendererOptions: {
1222                        autoscaleBubbles: this.options.autoscale == true,
1223                        bubbleGradients: true
1224                    },
1225                    shadow: true
1226                }
1227            });
1228        },
1229        refresh: function () {
1230            if (this.plot)
1231                this.plot.replot({ resetAxes: true });
1232        }
1233    });
1234
1235    my.BoxplotView = Backbone.View.extend({
1236        render: function () {
1237            this.$el.empty();
1238            var plotLabels = undefined;
1239            if (this.model && this.model.RowNames) {
1240                plotLabels = [];
1241                for (var i = 0; i < this.model.RowNames.length; i++) {
1242                    plotLabels.push({ label: this.model.RowNames[i] });
1243                }
1244            }
1245
1246            var values = this.model && this.model.Value ? this.model.Value : this.model;
1247            var globalMin = Number.MAX_VALUE;
1248            var globalMax = Number.MIN_VALUE;
1249
1250            var plotValues = [];
1251
1252            for (var key in values) {
1253                var entry = values[key];
1254                if ($.isArray(entry[0])) {
1255                    for (var i = 0; i < entry.length; i++) {
1256                        var cnt = entry[i].length;
1257                        var mean = _.mean(entry[i]);
1258                        var median = _.median(entry[i]);
1259                        var min = _.min(entry[i]);
1260                        var max = _.max(entry[i]);
1261                        var sorted = _.sortBy(entry[i], function (num) { return num; });
1262                        var q1 = percentile(25, sorted);
1263                        var q3 = percentile(75, sorted);
1264                        var diff = 1.5 * (q3 - q1);
1265
1266                        plotValues.push([key, min, q1, median, q3, max]);
1267                        //plotValues.push(["Sample " + i, percentile(15, sorted), q1, median, q3, percentile(85, sorted)]);
1268                        if (max > globalMax) globalMax = max;
1269                        if (min < globalMin) globalMin = min;
1270                    }
1271                } else {
1272                    var cnt = entry.length;
1273                    var mean = _.mean(entry);
1274                    var median = _.median(entry);
1275                    var min = _.min(entry);
1276                    var max = _.max(entry);
1277                    var sorted = _.sortBy(entry, function (num) { return num; });
1278                    var q1 = percentile(25, sorted);
1279                    var q3 = percentile(75, sorted);
1280                    plotValues.push([key, min, q1, median, q3, max]);
1281                    if (max > globalMax) globalMax = max;
1282                    if (min < globalMin) globalMin = min;
1283                }
1284            }
1285
1286
1287
1288            if (!this.$el.attr('id')) {
1289                this.$el.attr('id', my.PlotView.nextId());
1290            }
1291
1292            if (!this.$el.css('width'))
1293                this.$el.css('width', '500px');
1294
1295            this.plot = $.jqplot(this.$el.attr('id'), [plotValues], {
1296                cursor: {
1297                    show: true,
1298                    zoom: true,
1299                    showTooltip: false
1300                },
1301                series: [{ renderer: $.jqplot.BoxplotRenderer, rendererOptions: {}}],
1302                axesDefaults: {},
1303                axes: {
1304                    yaxis: {
1305                        min: globalMin * 0.9,
1306                        max: globalMax * 1.1
1307                    },
1308                    xaxis: {
1309                        renderer: $.jqplot.CategoryAxisRenderer
1310                    }
1311                },
1312                legend: {
1313                    show: true,
1314                    placement: 'outsideGrid'
1315                },
1316                highlighter: {
1317                    show: true,
1318                    sizeAdjust: 7.5
1319                }
1320            });
1321        },
1322        refresh: function () {
1323            if (this.plot)
1324                this.plot.replot({ resetAxes: true });
1325        }
1326    });
1327    return my;
1328} (OAAS_VIEW || {}, Backbone, _, $, OAAS_MODEL));
Note: See TracBrowser for help on using the repository browser.