var OAAS_VIEW = (function (my, Backbone, _, $, OAAS_MODEL) {
// ================ VIEWS =======================
my.ExperimentTreeView = Backbone.View.extend({
renderTree: function (model) {
var jsonModel = model.toJSON();
var self = this;
this.tree = $('
').dynatree({
children: [jsonModel],
autoExpandMS: 750,
onActivate: function (node) {
// if we are able to remove this node from the tree, enable the remove button.
if (node && node.parent.parent != null) {
$('.remove-button', this.$el).removeAttr("disabled");
} else if (node) {
$('.remove-button', this.$el).attr("disabled", "disabled");
}
var modelNode = OAAS_MODEL.ExperimentNode.lookup(node.data.key);
if (modelNode && !modelNode.get('isExperiment')) {
$('.variate-button', this.$el).removeAttr("disabled");
} else {
$('.variate-button', this.$el).attr("disabled", "disabled");
}
},
dnd: {
onDragStart: function (node) {
return node.parent.parent != null;
},
onDragEnter: function (node, sourceNode) {
return true;
},
onDragOver: function (node, sourceNode, hitMode) {
return node.data.title !== "Experiment" || hitMode === "over";
},
onDrop: function (node, sourceNode, hitMode, ui, draggable) {
if (hitMode !== 'over')
return;
var modelNode = OAAS_MODEL.ExperimentNode.lookup(node.data.key);
if (sourceNode) {
var sourceModelNode = OAAS_MODEL.ExperimentNode.lookup(sourceNode.data.key);
sourceNode.move(node, hitMode);
// move node into the modelNode
sourceModelNode.moveNode(modelNode);
}
else {
var elem = $(draggable.element[0]);
var newNode = { nodeId: elem.attr('id'), isExperiment: elem.attr('data-isExperiment') === 'true', title: elem.attr('data-name'), key: OAAS_MODEL.ExperimentNode.nextGlobalKey() };
if (hitMode == "over") {
node.addChild(newNode);
node.expand(true);
// update the data model
newNode = modelNode.addNode(newNode);
self.trigger('node-added', newNode);
}
}
self.trigger('structure-changed', modelNode);
}
}
});
this.tree.dynatree("getRoot").visit(function (node) {
node.expand(true);
});
},
initialize: function (spec) {
this.renderTree(spec.model);
},
events: {
'click .remove-button': 'doRemove',
'click .variate-button': 'doVariate'
},
render: function () {
this.$el.empty();
this.renderTree(this.model);
var content = $(_.template($('#stepwizard_template').html(), {}));
this.tree.appendTo(this.$el);
content.appendTo(this.$el);
},
doRemove: function () {
var node = this.tree.dynatree('getActiveNode');
if (node && node.parent.parent != null) {
var nxt = node.getNextSibling();
if (nxt == null)
nxt = node.getPrevSibling();
if (nxt == null)
nxt = node.getParent();
node.remove();
OAAS_MODEL.ExperimentNode.lookup(node.data.key).remove();
if (nxt) {
this.tree.dynatree("getTree").activateKey(nxt.data.key);
}
}
this.trigger('structure-changed', node);
},
doVariate: function () {
var node = this.tree.dynatree('getActiveNode');
var modelNode = OAAS_MODEL.ExperimentNode.lookup(node.data.key);
this.trigger('variation-request', {
parentNode: node.parent,
model: modelNode
});
}
});
my.ExperimentDetailsTreeView = Backbone.View.extend({
render: function () {
var jsonModel = this.model.toJSON();
var self = this;
var tree = $('').dynatree({
children: [jsonModel],
autoExpandMS: 750,
onActivate: function (node) {
// if we click a node, simply open a dialog to enter it's parameters
self.trigger('node-clicked', node);
}
});
this.$el.empty();
tree.appendTo(this.$el);
tree.dynatree("getRoot").visit(function (node) {
node.expand(true);
});
}
});
my.DraggableGroup = Backbone.View.extend({
initialize: function () {
this.collection.bind('remove', this.render, this);
this.collection.bind('add', this.render, this);
},
render: function () {
var self = this;
var div = $('');
if (this.collection.length == 0) {
$('').text("No items found!").appendTo(div);
}
for (var i = 0; i < this.collection.length; i++) {
new my.DraggableView({ model: this.collection.models[i], el: div }).render();
}
this.$el.empty();
div.appendTo(this.$el);
}
});
my.DraggableView = Backbone.View.extend({
render: function () {
var late = _.template($("#draggable_template").html(), this.model.attributes);
$(late).draggable({
revert: true,
connectToDynatree: true,
cursorAt: { top: -5, left: -5 },
helper: "clone"
}).appendTo(this.$el);
}
});
my.SelectableGroup = Backbone.View.extend({
initialize: function () {
this.collection.bind('remove', this.render, this);
this.collection.bind('add', this.render, this);
this.collection.bind('change', this.render, this);
},
render: function () {
var self = this;
var div = $('');
if (this.collection.length == 0) {
$('').text("No experiments found!").appendTo(div);
}
for (var i = 0; i < this.collection.length; i++) {
new my.SelectableView({ model: this.collection.models[i], el: div }).render();
}
this.$el.empty();
div.selectable({
filter: 'div',
selected: function (event, ui) {
self.selected(event, ui);
}
}).appendTo(this.$el);
},
selected: function (event, ui) {
var nodeId = $(ui.selected).attr('id');
this.trigger('node-selected', nodeId);
}
});
my.SelectableView = Backbone.View.extend({
render: function () {
var late = _.template($("#draggable_template").html(), this.model.attributes);
$(late).appendTo(this.$el);
}
});
my.StepWizardView = Backbone.View.extend({
render: function () {
var self = this;
this.$el.smartWizard({
keyNavigation: false,
onLeaveStep: function (step) {
var stepNum = parseInt(step.attr("rel"));
return self.validateStep(stepNum);
},
onShowStep: function (step) {
var stepNum = parseInt(step.attr("rel"));
return self.showStep(stepNum);
},
onFinish: function () {
self.trigger('experiment-finished');
}
});
},
validateStep: function (stepNumber) {
var validationModel = {
stepNumber: stepNumber,
succeeded: true
};
this.trigger('step-validated', validationModel);
return validationModel.succeeded;
},
showStep: function (stepNumber) {
this.trigger('step-shown', stepNumber);
return true;
}
});
my.ParameterWizard = Backbone.View.extend({
createWizard: function (steps) {
// use template instead of jquery?!
var newWizard = $('').addClass('wizard swMain');
var stepMenu = $('
');
var prefix = name + '_';
for (var i = 0; i < steps.length; i++) {
var step = steps[i];
var a = $('', { href: '#' + prefix + (i + 1) });
$('').addClass("stepNumber").text((i + 1)).appendTo(a);
$('').addClass("stepDesc").html(step.heading + ' ' + step.description + '').appendTo(a);
var li = $('').append(a);
li.appendTo(stepMenu);
}
stepMenu.appendTo(newWizard);
for (var i = 0; i < steps.length; i++) {
var div = $('', { id: prefix + (i + 1) });
if (steps[i].content && steps[i].content != null)
div.append(steps[i].content);
div.appendTo(newWizard);
}
return newWizard;
},
render: function () {
var self = this;
this.$el.empty();
var data = this.model.get('data');
var hasProblemParameters = typeof data.ProblemParameters !== 'undefined';
var dialog = $('');
var algoDiv = $('');
var problemDiv = $('');
for (var i = 0; i < data.AlgorithmParameters.length; i++) {
var parameterView = new my.ParameterEditableView({ model: data.AlgorithmParameters[i] });
// render to dialog
parameterView.render();
parameterView.$el.appendTo(algoDiv);
}
if (hasProblemParameters) {
var problemParameters = data.ProblemParameters;
for (var i = 0; i < problemParameters.length; i++) {
var parameterView = new my.ParameterEditableView({ model: problemParameters[i] });
// render to dialog
parameterView.render();
parameterView.$el.appendTo(problemDiv);
}
}
var newWizard =
hasProblemParameters ?
this.createWizard([
{ heading: "Algorithm Parameters", description: "Adjust algorithm parameters", content: algoDiv },
{ heading: "Problem Parameters", description: "Adjust problem parameters", content: problemDiv }
]) :
this.createWizard([
{ heading: "Algorithm Parameters", description: "Adjust algorithm parameters", content: algoDiv }
]);
newWizard.appendTo(this.$el);
newWizard.smartWizard({
keyNavigation: false,
onFinish: function () {
self.trigger('parameters-finished');
}
});
}
});
my.ParameterEditableView = Backbone.View.extend({
/*events: {
'change': 'valueChanged'
},*/
render: function () {
var mapper = new my.DatatypeMapper();
var content = $(_.template($('#parameter_template').html(), this.model));
mapper.mapHtml(this.model, $('.rightEntry', content));
content.appendTo(this.$el);
} /*,
valueChanged: function (param) {
var value = $(param.target).val();
if (!isNaN(value))
value = parseFloat(value);
var name = $(param.target).attr("name");
// normally a controller would do this, just for once, let the view update the model
var splitted = name.split("_");
if (splitted.length == 1) {
this.model.Value = value;
} else if (splitted.length == 2) {
this.model.Value[parseInt(splitted[1])] = value;
} else if (splitted.length == 3) {
this.model.Value[parseInt(splitted[1])][parseInt(splitted[2])] = value;
}
}*/
});
my.LoadingDialog = Backbone.View.extend({
initialize: function () {
this.reset();
},
render: function () {
var self = this;
this.$el.empty();
var dialog = $('');
this.content.appendTo(dialog);
dialog.appendTo(this.$el);
this.$el.dialog({
autoOpen: true
});
},
text: function (txt) {
$('p', this.$el).text(txt);
},
setLoading: function (isLoading) {
if (isLoading)
$('img', this.$el).show();
else
$('img', this.$el).hide();
},
reset: function () {
this.content = $(_.template($('#loading_template').html(), {}));
},
setContent: function (el) {
this.content = el;
},
close: function () {
this.$el.dialog("close");
}
});
my.ParameterDialog = Backbone.View.extend({
render: function () {
var self = this;
this.$el.empty();
var data = this.model.get('data');
var hasProblemParameters = typeof data.ProblemParameters !== 'undefined';
var dialog = $('');
var parameterWizard = new my.ParameterWizard({ model: this.model, el: dialog });
parameterWizard.render();
this.listenTo(parameterWizard, 'parameters-finished', function () {
self.trigger('parameters-finished');
});
dialog.appendTo(this.$el);
this.$el.dialog({
autoOpen: true,
width: 1024,
height: 768
});
},
close: function () {
this.$el.dialog("close");
}
});
my.ExperimentDetailsView = Backbone.View.extend({
events: {
'change': 'valueChanged'
},
valueChanged: function (param) {
var value = $(param.target).val();
if (!isNaN(value))
value = parseFloat(value);
var name = $(param.target).attr("name");
if (name == 'Name')
this.model.set({ title: value });
else if (name == 'RunImmediately')
this.model.set({ run: $(param.target).is(':checked') });
else if (name == 'Repititions')
this.model.set({ repititions: value });
else if (name == 'Group')
this.model.set({ group: value });
if (this.model.get('run')) {
$('input[name="Repititions"]', this.$el).removeAttr('disabled');
$('input[name="Group"]', this.$el).removeAttr('disabled');
} else {
$('input[name="Repititions"]', this.$el).attr('disabled', 'disabled');
$('input[name="Group"]', this.$el).attr('disabled', 'disabled');
}
},
render: function () {
$('input[name="Name"]', this.$el).val(this.model.get('title'));
$('input[name="RunImmediately"]', this.$el).attr('checked', this.model.get('run'));
$('input[name="Repititions"]', this.$el).val(this.model.get('repititions'));
$('input[name="Group"]', this.$el).val(this.model.get('group'));
}
});
my.ValidationHintsView = Backbone.View.extend({
render: function () {
var template = _.template($('#validationhints_template').html(), this.model);
$(template).dialog({
resizable: false,
modal: true,
buttons: {
"OK": function () {
$(this).dialog("close");
}
}
});
}
});
// ========== Variation Dialog Views ============
my.VariationDialog = Backbone.View.extend({
initialize: function (spec) {
},
events: {
'change': 'entrySelected'
},
render: function () {
var self = this;
var dialog = $(_.template($('#variationdialog_template').html(), {}));
var content = $('select[class="variationDialogContent"]', dialog);
var data = this.model.get('data');
for (var i = 0; i < data.AlgorithmParameters.length; i++) {
data.AlgorithmParameters[i].Index = i;
var entry = new my.VariationEntryView({ model: data.AlgorithmParameters[i], el: content });
entry.render();
}
this.details = $('div[class="variationDetails"]', dialog);
this.activateButton = $('input[name="active"]', dialog);
this.activateButton.change(function (evt) {
var checked = $(evt.target).is(':checked');
if (self.selectedModel && self.selectedModel != null) {
self.selectedModel.Active = checked;
}
if (self.selectedModel.Active) {
$('*', self.details).removeAttr('disabled');
}
else {
//$('input,button,select', self.details).attr('disabled', 'disabled');
$('*', self.details).attr('disabled', 'disabled');
}
});
dialog.dialog({
height: 300,
buttons: {
"Abort": function () {
$(this).dialog("close");
},
"OK": function () {
self.generateVariations();
$(this).dialog("close");
}
}
});
content.change(function (evt) {
var optionSelected = $("option:selected", this);
var model = self.model.get('data').AlgorithmParameters[parseInt($(optionSelected).attr('data-index'))]
self.entrySelected(model);
});
this.variationDetails = new my.VariationContentView({ el: this.details });
var model = this.model.get('data').AlgorithmParameters[0];
this.entrySelected(model);
},
entrySelected: function (model) {
this.selectedModel = model;
this.variationDetails.model = model;
this.details.empty();
this.variationDetails.render();
if (model.Active) {
this.activateButton.attr('checked', 'checked');
$('*', this.details).removeAttr('disabled');
}
else {
this.activateButton.removeAttr('checked');
$('*', this.details).attr('disabled', 'disabled');
}
},
generateVariations: function () {
this.solutions = [];
this.exhaust(0, []);
this.trigger('variations-generated', this.solutions);
},
exhaust: function (index, element) {
if (index == this.model.get('data').AlgorithmParameters.length) {
// we found a solution! store it by creating a deep copy
this.solutions.push($.extend(true, [], element));
return;
}
var currentParameter = this.model.get('data').AlgorithmParameters[index];
if (currentParameter.Active && currentParameter.Generated) {
for (var i = 0; i < currentParameter.Generated.length; i++) {
element[index] = {
Name: currentParameter.Name,
Value: currentParameter.Generated[i]
};
if (currentParameter.Options) {
element[index].Options = currentParameter.Options;
}
this.exhaust(index + 1, element);
}
} else {
element[index] = {
Name: currentParameter.Name,
Value: currentParameter.Value
};
if (currentParameter.Options) {
element[index].Options = currentParameter.Options;
}
this.exhaust(index + 1, element);
}
}
});
my.TableEditView = Backbone.View.extend({
events: {
'change': 'valueChanged',
'click button[data-operation="Add"]': 'addClicked',
'click button[data-operation="Remove"]': 'removeClicked'
},
render: function () {
this.$el.empty();
// http: //stackoverflow.com/questions/8749236/create-table-with-jquery-append
var table = $('
').addClass('editableTable');
// determine dimensions
var rows = this.model.length;
var columns = 1;
if ($.isArray(this.model[0])) {
columns = this.model[0].length;
}
// create head elements
var head = $('');
var headerRow = $('
');
headerRow.appendTo(head);
if (this.options.rowNames) {
var rowNames = this.options.rowNames;
for (var i = 0; i < rowNames.length; i++) {
$('
').text(rowNames[i]).appendTo(headerRow);
}
} else {
for (var i = 0; i < columns; i++) {
$('
').text((i + 1) + '. Column').appendTo(headerRow);
}
}
head.appendTo(table);
// create body
var body = $('');
for (var i = 0; i < rows; i++) {
var row = $('
');
for (var j = 0; j < columns; j++) {
var rowContent = this.model[i];
var columnEntry = $.isArray(rowContent) ? rowContent[j] : rowContent;
var col = $('
');
if (this.options.editable) {
$('').val(columnEntry).appendTo(col);
} else {
col.text(columnEntry);
}
col.appendTo(row);
}
if (this.options.editable) {
$('').val(columnEntry).text('+').appendTo(col);
$('').val(columnEntry).text('-').appendTo(col);
}
row.appendTo(body);
}
body.appendTo(table);
table.appendTo(this.$el);
if (this.options.useDatatable)
table.dataTable();
},
valueChanged: function (evt) {
var target = $(evt.target);
var value = target.val();
var index = target.attr('name');
var splittedName = index.split('_');
var i = parseInt(splittedName[0]);
var j = parseInt(splittedName[1]);
if ($.isArray(this.model[i])) {
this.model[i][j] = parseFloat(value);
} else {
this.model[i] = parseFloat(value);
}
},
addClicked: function (evt) {
var target = $(evt.target);
var index = target.attr('name');
var i = parseInt(index);
if ($.isArray(this.model[i])) {
var cpy = [];
for (var j = 0; j < this.model[i].length; j++) {
cpy.push(this.model[i][j]);
}
this.model.splice(i, 0, cpy);
} else {
this.model.splice(i, 0, this.model[i]);
}
// render after model changes
this.render();
},
removeClicked: function (evt) {
var target = $(evt.target);
var index = target.attr('name');
var i = parseInt(index);
this.model.splice(i, 1);
// render after model changes
this.render();
}
});
my.NumberEditableView = Backbone.View.extend({
events: {
'change': 'valueChanged'
},
render: function () {
$('').val(this.model.Value).appendTo(this.$el);
},
valueChanged: function (evt) {
var value = $(evt.target).val();
if (!isNaN(value)) {
value = parseFloat(value);
this.model.Value = value;
} else {
$('input', this.$el).val(this.model.Value);
}
}
});
my.SelectionView = Backbone.View.extend({
events: {
'change': 'checked'
},
render: function () {
this.$el.empty();
var s = $('').attr('data-name', this.model.Name).appendTo(this.$el);
for (var i = 0; i < this.model.Options.length; i++) {
$('', { value: this.model.Options[i], text: this.model.Options[i] }).appendTo(s);
}
s.val(this.model.Value);
},
checked: function (evt) {
var value = $(evt.target).val();
var name = $(evt.target).attr('data-name')
this.trigger('selected', { name: name, value: value });
}
});
my.OptionSelectionView = Backbone.View.extend({
events: {
'change': 'checked'
},
render: function () {
this.$el.empty();
if ($.isArray(this.model)) {
for (var i = 0; i < this.model.length; i++) {
$(_.template($('#checkbox_template').html(), { checked: false, name: this.model[i], text: this.model[i], hideText: this.options.hideText })).appendTo(this.$el);
}
}
else {
$(_.template($('#checkbox_template').html(), { checked: this.model, name: this.model, text: this.model, hideText: this.options.hideText })).appendTo(this.$el);
}
},
checked: function (evt) {
var checked = $(evt.target).is(':checked');
var name = $(evt.target).attr('name')
this.trigger('changed', { name: name, checked: checked });
}
});
my.VariationContentView = Backbone.View.extend({
render: function () {
var self = this;
if (this.model.Value === 'false' || this.model.Value === 'true' ||
this.model.Value === true || this.model.Value === false) {
$(_.template($('#variation_boolean_template').html(), {})).appendTo(this.$el);
this.model.Generated = [false, true];
}
else if (!isNaN(this.model.Value)) {
var elem = $(_.template($('#variation_number_template').html(), {}));
elem.appendTo(this.$el)
$('input[name="generate"]', elem).click(function (evt) { self.openGenerator(evt); });
if (!this.model.Generated) {
this.model.Generated = [0, 1, 2];
}
var tev = new my.TableEditView({ model: this.model.Generated, el: $('div div', this.$el), editable: true });
tev.render();
}
else if (this.model.Options) {
var osv = new my.OptionSelectionView({ model: this.model.Options, el: this.$el });
this.listenTo(osv, 'changed', function (evt) {
self.selectionChanged(evt);
});
if (!this.model.Generated)
this.model.Generated = [];
osv.render();
for (var i = 0; i < this.model.Generated.length; i++) {
$('input[name="' + this.model.Generated[i] + '"]', this.$el).attr('checked', 'checked');
}
}
},
openGenerator: function (evt) {
var self = this;
var generator = new my.GeneratorDialog();
this.listenTo(generator, 'success', function (data) {
self.doGenerate(data);
});
generator.render();
},
doGenerate: function (data) {
if (data.minimum > data.maximum) {
var tmp = data.minimum;
data.minimum = data.maximum;
data.maximum = tmp;
}
if (data.step < 0)
data.step *= -1;
if (data.step == 0)
data.step = 1;
this.model.Generated = _.range(data.minimum, data.maximum + 1, data.step);
var tev = new my.TableEditView({ model: this.model.Generated, el: $('div div', this.$el), editable: true });
tev.render();
},
selectionChanged: function (evt) {
if (!evt.checked) {
this.model.Generated = _.without(this.model.Generated, evt.name);
} else if (_.indexOf(this.model.Generated, evt.name) == -1) {
this.model.Generated.push(evt.name);
}
}
});
my.GeneratorDialog = Backbone.View.extend({
render: function () {
var self = this;
var generator = $(_.template($('#variation_generator_template').html(), {}));
generator.dialog({
buttons: {
"Abort": function () {
$(this).dialog("close");
},
"OK": function () {
$(this).dialog("close");
self.trigger("success", {
minimum: parseInt($('input[name="minimum"]', generator).val()),
maximum: parseInt($('input[name="maximum"]', generator).val()),
step: parseFloat($('input[name="step"]', generator).val())
});
}
}
});
}
});
my.VariationEntryView = Backbone.View.extend({
render: function () {
var entryTemplate = _.template($('#variationentry_template').html(), this.model);
$(entryTemplate).appendTo(this.$el);
}
});
// ========================== Run Collection Views ===================================
function percentile(p, data) {
var i = (data.length * p) / 100 + 0.5;
var f = i - parseInt(i);
var k = Math.floor(i) - 1;
return (1 - f) * data[k] + f * data[k + 1];
}
my.RunCollectionView = Backbone.View.extend({
events: {
'change .x': 'xAxisSelected',
'change .y': 'yAxisSelected',
'change .plotType': 'plotSelected',
'change .datatableOptions': 'datatableSelected',
'change .datatableRow': 'datatableRowSelected',
'change .bubbleSize': 'bubbleTypeSelected'
},
render: function () {
if (this.model.models.length == 0)
return;
var self = this;
var ele = $(_.template($('#runcollection_template').html(), {}));
var xAxisSelect = $('.x', ele);
var yAxisSelect = $('.y', ele);
var results = this.model.models[0].get('results');
this.options.datatables = {};
var datatableSelect = $('.datatableOptions', ele);
var names = [];
var previouslyFound = { results: {}, params: {} };
_.each(this.model.models, function (model) {
_.each(model.get('results'), function (res) {
if (!previouslyFound['results'][res.Name]) {
previouslyFound['results'][res.Name] = true;
names.push({ name: res.Name, source: 'results' });
}
});
_.each(model.get('params'), function (res) {
if (!previouslyFound['params'][res.Name]) {
previouslyFound['params'][res.Name] = true;
names.push({ name: res.Name, source: 'params' });
}
});
});
previouslyFound = {};
this.options.modelMapping = names;
this.bubbleSelect = $('select[class="bubbleSize"]', ele);
_.each(names, function (entry) {
var option = $("", { value: entry.name, text: entry.name });
option.clone().appendTo(xAxisSelect);
option.clone().appendTo(yAxisSelect);
option.clone().appendTo(self.bubbleSelect);
var rowNames = null;
var foundModel = _.find(self.model.models, function (model) {
var e = _.find(model.get(entry.source), function (e) { return e.Name == entry.name && e.RowNames });
if (e)
rowNames = e.RowNames;
return e && e.RowNames;
});
if (rowNames) {
self.options.datatables[entry.name] = rowNames;
$('', { value: entry.name, text: entry.name }).appendTo(datatableSelect);
}
});
// prepare slider settings
this.options.bubbleSize = 10;
this.options.xAxis = this.model.models[0].get('results')[0].Name;
this.options.yAxis = this.options.xAxis;
this.options.selectedXIndex = 0;
this.options.selectedYIndex = 0;
this.options.plot = 'Boxplot';
$('div[class="bubbleSlider"]', ele).slider({
range: "max",
min: 10,
max: 40,
value: 10,
stop: function (event, ui) {
self.options.bubbleSize = ui.value;
self.createPlot();
}
}).css('width', '120px').css('margin-left', '15px').css('margin-right', '15px').css('display', 'inline-block');
ele.appendTo(this.$el);
// init resizer
this.plotWidth = 500;
this.plotHeight = 500;
var parent = $('div[class="resizer"]', this.$el);
this.resizerView = new my.ResizableView({ el: parent });
this.listenTo(this.resizerView, 'resized', function (evt) {
self.updatePlot(
$(evt.target).width() * 0.96,
$(evt.target).height() * 0.96
);
});
this.resizerView.render();
this.updateRowSelect(datatableSelect.val());
this.createPlot();
this.updatePlot(
600,
500
);
},
updateRowSelect: function (name) {
if (!name)
return;
var rowSelect = $('select[class="datatableRow"]', this.$el);
rowSelect.empty();
var rownames = this.options.datatables[name];
for (var i = 0; i < rownames.length; i++) {
$('', { value: rownames[i], text: rownames[i] }).appendTo(rowSelect);
}
this.options.selectedDatatable = name;
this.options.datatableRow = rowSelect.val();
this.createPlot();
},
datatableSelected: function (evt) {
var value = $(evt.target).val();
this.options.selectedDatatable = value;
this.updateRowSelect(value);
},
datatableRowSelected: function (evt) {
var value = $(evt.target).val();
this.options.datatableRow = value;
this.createPlot();
},
xAxisSelected: function (evt) {
var target = $(evt.target);
this.options.selectedXIndex = target.prop("selectedIndex");
this.options.xAxis = target.val();
this.createPlot();
},
yAxisSelected: function (evt) {
var target = $(evt.target);
this.options.selectedYIndex = target.prop("selectedIndex");
this.options.yAxis = target.val();
this.createPlot();
},
plotSelected: function (evt) {
var target = $(evt.target);
this.options.plot = target.val();
this.createPlot();
},
bubbleTypeSelected: function (evt) {
this.createPlot();
},
updateVisibility: function (evt) {
if (this.options.plot == 'Boxplot') {
$('span[class="choice"]', this.$el).show();
$('span[class="bubble"]', this.$el).hide();
$('span[class="datatable"]', this.$el).hide();
} else if (this.options.plot == 'Bubblechart') {
$('span[class="choice"]', this.$el).show();
$('span[class="bubble"]', this.$el).show();
$('span[class="datatable"]', this.$el).hide();
} else if (this.options.plot == 'Datatable') {
$('span[class="choice"]', this.$el).hide();
$('span[class="bubble"]', this.$el).hide();
$('span[class="datatable"]', this.$el).show();
}
},
updatePlot: function (width, height) {
this.plotDiv.height(height);
this.plotDiv.width(width);
this.plotWidth = width;
this.plotHeight = height;
this.plot.refresh();
},
createPlot: function () {
if (this.options.plot && this.options.xAxis && this.options.yAxis) {
if (this.options.plot == 'Boxplot') {
this.createBoxplot();
} else if (this.options.plot == 'Bubblechart') {
this.createBubblechart();
} else if (this.options.plot == 'Datatable') {
this.createDatatable();
}
this.updateVisibility();
}
},
preparePlotDiv: function () {
var parent = $('div[class="plot"]', this.$el);
parent.empty();
this.plotDiv = $('').css('width', this.plotWidth).css('height', this.plotHeight).appendTo(parent);
return this.plotDiv;
},
createBoxplot: function () {
var self = this;
var parameterXSource = this.options.modelMapping[this.options.selectedXIndex].source; // either 'params' or 'results'
var parameterYSource = this.options.modelMapping[this.options.selectedYIndex].source; // either 'params' or 'results'
// prepare data for boxplot
var values = {};
for (var i = 0; i < this.model.models.length; i++) {
var xlabel = _.find(this.model.models[i].get(parameterXSource), function (itm) { return itm.Name == self.options.xAxis });
if (!xlabel)
continue;
if ($.isArray(xlabel.Value))
values[parameterXSource + '.' + xlabel.Name + ' = ' + xlabel.Value[0]] = [];
else
values[parameterXSource + '.' + xlabel.Name + ' = ' + xlabel.Value] = [];
}
for (var i = 0; i < this.model.models.length; i++) {
var xlabel = _.find(this.model.models[i].get(parameterXSource), function (itm) { return itm.Name == self.options.xAxis });
var entry = _.find(this.model.models[i].get(parameterYSource), function (itm) { return itm.Name == self.options.yAxis });
if (!xlabel || !entry || !entry.Value)
continue;
var index = parameterXSource + '.' + xlabel.Name + ' = ' + ($.isArray(xlabel.Value) ? xlabel.Value[0] : xlabel.Value);
if ($.isArray(entry.Value))
values[index].push(entry.Value[0]);
else
values[index].push(entry.Value);
}
// create boxplot
var div = this.preparePlotDiv();
var boxPlot = new my.BoxplotView({ model: values, el: div });
this.plot = boxPlot;
boxPlot.render();
},
createBubblechart: function () {
var self = this;
var parameterXSource = this.options.modelMapping[this.options.selectedXIndex].source; // either 'params' or 'results'
var parameterYSource = this.options.modelMapping[this.options.selectedYIndex].source; // either 'params' or 'results'
// prepare data for bubble chart
var values = [];
var autoscaleBubbles = this.bubbleSelect.val() != 'Constant';
for (var i = 0; i < this.model.models.length; i++) {
var xValue = _.find(this.model.models[i].get(parameterXSource), function (itm) { return itm.Name == self.options.xAxis });
var yValue = _.find(this.model.models[i].get(parameterYSource), function (itm) { return itm.Name == self.options.yAxis });
if (!xValue || !yValue)
continue;
// determine size
var size = 0;
if (this.bubbleSelect.val() == 'Constant') {
size = this.options.bubbleSize;
} else {
var valueProvider = _.find(this.model.models[i].get('results'), function (itm) { return itm.Name == self.bubbleSelect.val(); });
if (!valueProvider)
valueProvider = _.find(this.model.models[i].get('params'), function (itm) { return itm.Name == self.bubbleSelect.val(); });
if (!valueProvider) {
size = this.options.bubbleSize;
} else {
var value = valueProvider.Value;
while ($.isArray(value)) {
value = value[0];
}
size = parseInt(value.replace(/,/, '.'));
if (isNaN(size))
size = this.options.bubbleSize;
}
}
// add element
var xFloat = parseFloat(xValue.Value.replace(/,/, '.'));
var yFloat = parseFloat(yValue.Value.replace(/,/, '.'));
values.push([xFloat, yFloat, size, 'Run' + (i + 1)]);
}
// render bubble chart
var div = this.preparePlotDiv();
var bubblePlot = new my.BubbleplotView({ model: values, el: div, autoscale: autoscaleBubbles });
this.plot = bubblePlot;
bubblePlot.render();
},
createDatatable: function () {
if (!this.options.selectedDatatable)
return;
var self = this;
// prepare data for datatable
var values = [];
var rowNames = [];
for (var i = 0; i < this.model.models.length; i++) {
var table = _.find(this.model.models[i].get('results'), function (itm) { return itm.Name == self.options.selectedDatatable });
if (!table)
table = _.find(this.model.models[i].get('params'), function (itm) { return itm.Name == self.options.selectedDatatable });
if (!table)
continue;
var index = table.RowNames.indexOf(this.options.datatableRow);
if (index == -1)
continue;
var entries = [];
for (var j = 0; j < table.Value.length; j++) {
entries.push(table.Value[j][index]);
}
values.push(entries);
rowNames.push(this.options.datatableRow + ' of Run Number ' + (i + 1));
}
// render datatable
var div = this.preparePlotDiv();
var plot = new my.PlotView({ model: values, el: div, rownames: rowNames });
this.plot = plot;
plot.render();
}
});
// ===================== Plots =============================
my.PlotView = Backbone.View.extend({
render: function () {
this.$el.empty();
var plotLabels = undefined;
var labelSource = this.model.RowNames ? this.model.RowNames : this.options.rownames;
if (labelSource) {
plotLabels = [];
for (var i = 0; i < labelSource.length; i++) {
plotLabels.push({ label: labelSource[i] });
}
}
var values = this.model.Value ? this.model.Value : this.model;
var plotValues = [];
if (!this.options.transpose) {
if ($.isArray(values[0])) {
for (var i = 0; i < values.length; i++) {
plotValues.push(values[i]);
}
} else {
plotValues.push(values);
}
}
else {
if ($.isArray(values[0])) {
var columnCount = values[0].length;
var colVals = {};
for (var i = 0; i < columnCount; i++)
colVals[i] = [];
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < columnCount; j++) {
colVals[j].push(values[i][j]);
}
}
for (var i = 0; i < columnCount; i++) {
plotValues.push(colVals[i]);
}
} else {
plotValues.push(values);
}
}
if (!this.$el.attr('id')) {
this.$el.attr('id', my.PlotView.nextId());
}
if (!this.$el.css('width'))
this.$el.css('width', '500px');
this.plot = $.jqplot(this.$el.attr('id'), plotValues, {
cursor: {
show: true,
zoom: true,
showTooltip: false
},
series: plotLabels,
seriesDefaults: {
lineWidth: 1.5,
markerOptions: {
size: 2,
lineWidth: 2
}
},
legend: {
show: true,
placement: 'outsideGrid'
},
highlighter: {
show: true,
sizeAdjust: 7.5
}
});
},
refresh: function () {
if (this.plot)
this.plot.replot({ resetAxes: true });
}
},
{
plotId: 1,
nextId: function () {
return my.PlotView.plotId++;
}
});
my.BubbleplotView = Backbone.View.extend({
render: function () {
this.$el.empty();
var values = this.model && this.model.Value ? this.model.Value : this.model;
var plotValues = [];
if (!this.$el.attr('id')) {
this.$el.attr('id', my.PlotView.nextId());
}
if (!this.$el.css('width'))
this.$el.css('width', '500px');
this.plot = $.jqplot(this.$el.attr('id'), [values], {
cursor: {
show: true,
zoom: true,
showTooltip: false
},
seriesDefaults: { renderer: $.jqplot.BubbleRenderer,
rendererOptions: {
autoscaleBubbles: this.options.autoscale == true,
bubbleGradients: true
},
shadow: true
}
});
},
refresh: function () {
if (this.plot)
this.plot.replot({ resetAxes: true });
}
});
my.BoxplotView = Backbone.View.extend({
render: function () {
this.$el.empty();
var plotLabels = undefined;
if (this.model && this.model.RowNames) {
plotLabels = [];
for (var i = 0; i < this.model.RowNames.length; i++) {
plotLabels.push({ label: this.model.RowNames[i] });
}
}
var values = this.model && this.model.Value ? this.model.Value : this.model;
var globalMin = Number.MAX_VALUE;
var globalMax = Number.MIN_VALUE;
var plotValues = [];
for (var key in values) {
var entry = values[key];
if ($.isArray(entry[0])) {
for (var i = 0; i < entry.length; i++) {
var cnt = entry[i].length;
var mean = _.mean(entry[i]);
var median = _.median(entry[i]);
var min = _.min(entry[i]);
var max = _.max(entry[i]);
var sorted = _.sortBy(entry[i], function (num) { return num; });
var q1 = percentile(25, sorted);
var q3 = percentile(75, sorted);
var diff = 1.5 * (q3 - q1);
plotValues.push([key, min, q1, median, q3, max]);
//plotValues.push(["Sample " + i, percentile(15, sorted), q1, median, q3, percentile(85, sorted)]);
if (max > globalMax) globalMax = max;
if (min < globalMin) globalMin = min;
}
} else {
var cnt = entry.length;
var mean = _.mean(entry);
var median = _.median(entry);
var min = _.min(entry);
var max = _.max(entry);
var sorted = _.sortBy(entry, function (num) { return num; });
var q1 = percentile(25, sorted);
var q3 = percentile(75, sorted);
plotValues.push([key, min, q1, median, q3, max]);
if (max > globalMax) globalMax = max;
if (min < globalMin) globalMin = min;
}
}
if (!this.$el.attr('id')) {
this.$el.attr('id', my.PlotView.nextId());
}
if (!this.$el.css('width'))
this.$el.css('width', '500px');
this.plot = $.jqplot(this.$el.attr('id'), [plotValues], {
cursor: {
show: true,
zoom: true,
showTooltip: false
},
series: [{ renderer: $.jqplot.BoxplotRenderer, rendererOptions: {}}],
axesDefaults: {},
axes: {
yaxis: {
min: globalMin * 0.9,
max: globalMax * 1.1
},
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer
}
},
legend: {
show: true,
placement: 'outsideGrid'
},
highlighter: {
show: true,
sizeAdjust: 7.5
}
});
},
refresh: function () {
if (this.plot)
this.plot.replot({ resetAxes: true });
}
});
return my;
} (OAAS_VIEW || {}, Backbone, _, $, OAAS_MODEL));