Index: branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionParameterAnalysisView.Designer.cs
===================================================================
--- branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionParameterAnalysisView.Designer.cs	(revision 14751)
+++ branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionParameterAnalysisView.Designer.cs	(revision 14751)
@@ -0,0 +1,400 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+namespace HeuristicLab.Optimization.Views {
+  partial class RunCollectionParameterAnalysisView {
+    /// <summary>
+    /// Required designer variable.
+    /// </summary>
+    private System.ComponentModel.IContainer components = null;
+
+    /// <summary>
+    /// Clean up any resources being used.
+    /// </summary>
+    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+    protected override void Dispose(bool disposing) {
+      if (disposing && (components != null)) {
+        components.Dispose();
+      }
+      base.Dispose(disposing);
+    }
+
+    #region Windows Form Designer generated code
+
+    /// <summary>
+    /// Required method for Designer support - do not modify
+    /// the contents of this method with the code editor.
+    /// </summary>
+    private void InitializeComponent() {
+      this.components = new System.ComponentModel.Container();
+      System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
+      System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend();
+      System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
+      System.Windows.Forms.DataVisualization.Charting.Title title1 = new System.Windows.Forms.DataVisualization.Charting.Title();
+      this.chart = new HeuristicLab.Visualization.ChartControlsExtensions.EnhancedChart();
+      this.stepSizeLabel = new System.Windows.Forms.Label();
+      this.stepSizeTextBox = new System.Windows.Forms.TextBox();
+      this.toolTip = new System.Windows.Forms.ToolTip(this.components);
+      this.logScalingCheckBox = new System.Windows.Forms.CheckBox();
+      this.errorProvider = new System.Windows.Forms.ErrorProvider(this.components);
+      this.groupsTreeView = new System.Windows.Forms.TreeView();
+      this.splitContainer = new System.Windows.Forms.SplitContainer();
+      this.splitContainer1 = new System.Windows.Forms.SplitContainer();
+      this.parametersGroupBox = new System.Windows.Forms.GroupBox();
+      this.parametersTreeView = new System.Windows.Forms.TreeView();
+      this.groupsGroupBox = new System.Windows.Forms.GroupBox();
+      this.removeGroupButton = new System.Windows.Forms.Button();
+      this.addGroupButton = new System.Windows.Forms.Button();
+      this.dataRowsGroupBox = new System.Windows.Forms.GroupBox();
+      this.minMaxCheckBox = new System.Windows.Forms.CheckBox();
+      this.medianCheckBox = new System.Windows.Forms.CheckBox();
+      this.quartilesCheckBox = new System.Windows.Forms.CheckBox();
+      this.averageCheckBox = new System.Windows.Forms.CheckBox();
+      ((System.ComponentModel.ISupportInitialize)(this.chart)).BeginInit();
+      ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).BeginInit();
+      ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
+      this.splitContainer.Panel1.SuspendLayout();
+      this.splitContainer.Panel2.SuspendLayout();
+      this.splitContainer.SuspendLayout();
+      ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
+      this.splitContainer1.Panel1.SuspendLayout();
+      this.splitContainer1.Panel2.SuspendLayout();
+      this.splitContainer1.SuspendLayout();
+      this.parametersGroupBox.SuspendLayout();
+      this.groupsGroupBox.SuspendLayout();
+      this.dataRowsGroupBox.SuspendLayout();
+      this.SuspendLayout();
+      // 
+      // chart
+      // 
+      this.chart.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+      chartArea1.AxisX.IsStartedFromZero = false;
+      chartArea1.AxisX.LabelStyle.Format = "N0";
+      chartArea1.AxisX.MinorGrid.Enabled = true;
+      chartArea1.AxisX.MinorGrid.LineColor = System.Drawing.Color.WhiteSmoke;
+      chartArea1.AxisX.MinorGrid.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dash;
+      chartArea1.AxisX.ScaleBreakStyle.Enabled = true;
+      chartArea1.AxisX.Title = "Evaluated Solutions";
+      chartArea1.AxisY.LabelStyle.Format = "P0";
+      chartArea1.AxisY.MinorGrid.Enabled = true;
+      chartArea1.AxisY.MinorGrid.LineColor = System.Drawing.Color.WhiteSmoke;
+      chartArea1.AxisY.MinorGrid.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dash;
+      chartArea1.AxisY.Title = "Relative Distance to Best Known Quality";
+      chartArea1.CursorX.Interval = 0D;
+      chartArea1.CursorX.IsUserSelectionEnabled = true;
+      chartArea1.CursorY.Interval = 0D;
+      chartArea1.CursorY.IsUserSelectionEnabled = true;
+      chartArea1.Name = "chartArea";
+      this.chart.ChartAreas.Add(chartArea1);
+      this.chart.Cursor = System.Windows.Forms.Cursors.Default;
+      legend1.Alignment = System.Drawing.StringAlignment.Center;
+      legend1.Docking = System.Windows.Forms.DataVisualization.Charting.Docking.Top;
+      legend1.Name = "legend";
+      this.chart.Legends.Add(legend1);
+      this.chart.Location = new System.Drawing.Point(3, 0);
+      this.chart.Name = "chart";
+      series1.ChartArea = "chartArea";
+      series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.FastLine;
+      series1.Legend = "legend";
+      series1.Name = "Series1";
+      this.chart.Series.Add(series1);
+      this.chart.Size = new System.Drawing.Size(735, 852);
+      this.chart.TabIndex = 0;
+      this.chart.Text = "chart";
+      title1.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+      title1.Name = "title";
+      this.chart.Titles.Add(title1);
+      this.chart.CustomizeLegend += new System.EventHandler<System.Windows.Forms.DataVisualization.Charting.CustomizeLegendEventArgs>(this.chart_CustomizeLegend);
+      this.chart.MouseDown += new System.Windows.Forms.MouseEventHandler(this.chart_MouseDown);
+      this.chart.MouseMove += new System.Windows.Forms.MouseEventHandler(this.chart_MouseMove);
+      // 
+      // stepSizeLabel
+      // 
+      this.stepSizeLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+      this.stepSizeLabel.AutoSize = true;
+      this.stepSizeLabel.Location = new System.Drawing.Point(3, 831);
+      this.stepSizeLabel.Name = "stepSizeLabel";
+      this.stepSizeLabel.Size = new System.Drawing.Size(55, 13);
+      this.stepSizeLabel.TabIndex = 1;
+      this.stepSizeLabel.Text = "&Step Size:";
+      // 
+      // stepSizeTextBox
+      // 
+      this.stepSizeTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+      this.stepSizeTextBox.Location = new System.Drawing.Point(82, 828);
+      this.stepSizeTextBox.Name = "stepSizeTextBox";
+      this.stepSizeTextBox.Size = new System.Drawing.Size(100, 20);
+      this.stepSizeTextBox.TabIndex = 2;
+      this.toolTip.SetToolTip(this.stepSizeTextBox, "The step size of evaluated solutions on the x-axis.");
+      this.stepSizeTextBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.stepSizeTextBox_KeyDown);
+      this.stepSizeTextBox.Validating += new System.ComponentModel.CancelEventHandler(this.stepSizeTextBox_Validating);
+      this.stepSizeTextBox.Validated += new System.EventHandler(this.stepSizeTextBox_Validated);
+      // 
+      // logScalingCheckBox
+      // 
+      this.logScalingCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+      this.logScalingCheckBox.AutoSize = true;
+      this.logScalingCheckBox.Location = new System.Drawing.Point(206, 830);
+      this.logScalingCheckBox.Name = "logScalingCheckBox";
+      this.logScalingCheckBox.Size = new System.Drawing.Size(118, 17);
+      this.logScalingCheckBox.TabIndex = 3;
+      this.logScalingCheckBox.Text = "&Logarithmic Scaling";
+      this.toolTip.SetToolTip(this.logScalingCheckBox, "Logarithmic scaling on x-axis.");
+      this.logScalingCheckBox.UseVisualStyleBackColor = true;
+      this.logScalingCheckBox.CheckedChanged += new System.EventHandler(this.logScalingCheckBox_CheckedChanged);
+      // 
+      // errorProvider
+      // 
+      this.errorProvider.BlinkStyle = System.Windows.Forms.ErrorBlinkStyle.NeverBlink;
+      this.errorProvider.ContainerControl = this;
+      // 
+      // groupsTreeView
+      // 
+      this.groupsTreeView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+      this.groupsTreeView.CheckBoxes = true;
+      this.groupsTreeView.HideSelection = false;
+      this.groupsTreeView.Location = new System.Drawing.Point(6, 49);
+      this.groupsTreeView.Name = "groupsTreeView";
+      this.groupsTreeView.ShowRootLines = false;
+      this.groupsTreeView.Size = new System.Drawing.Size(255, 524);
+      this.groupsTreeView.TabIndex = 2;
+      this.groupsTreeView.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(this.groupsTreeView_AfterCheck);
+      this.groupsTreeView.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.groupsTreeView_AfterSelect);
+      // 
+      // splitContainer
+      // 
+      this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
+      this.splitContainer.Location = new System.Drawing.Point(0, 0);
+      this.splitContainer.Name = "splitContainer";
+      // 
+      // splitContainer.Panel1
+      // 
+      this.splitContainer.Panel1.Controls.Add(this.splitContainer1);
+      // 
+      // splitContainer.Panel2
+      // 
+      this.splitContainer.Panel2.Controls.Add(this.dataRowsGroupBox);
+      this.splitContainer.Panel2.Controls.Add(this.stepSizeTextBox);
+      this.splitContainer.Panel2.Controls.Add(this.stepSizeLabel);
+      this.splitContainer.Panel2.Controls.Add(this.logScalingCheckBox);
+      this.splitContainer.Panel2.Controls.Add(this.chart);
+      this.splitContainer.Size = new System.Drawing.Size(1101, 851);
+      this.splitContainer.SplitterDistance = 273;
+      this.splitContainer.TabIndex = 0;
+      // 
+      // splitContainer1
+      // 
+      this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
+      this.splitContainer1.Location = new System.Drawing.Point(0, 0);
+      this.splitContainer1.Name = "splitContainer1";
+      this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal;
+      // 
+      // splitContainer1.Panel1
+      // 
+      this.splitContainer1.Panel1.Controls.Add(this.parametersGroupBox);
+      // 
+      // splitContainer1.Panel2
+      // 
+      this.splitContainer1.Panel2.Controls.Add(this.groupsGroupBox);
+      this.splitContainer1.Size = new System.Drawing.Size(273, 851);
+      this.splitContainer1.SplitterDistance = 261;
+      this.splitContainer1.TabIndex = 0;
+      // 
+      // parametersGroupBox
+      // 
+      this.parametersGroupBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+      this.parametersGroupBox.Controls.Add(this.parametersTreeView);
+      this.parametersGroupBox.Location = new System.Drawing.Point(3, 3);
+      this.parametersGroupBox.Name = "parametersGroupBox";
+      this.parametersGroupBox.Size = new System.Drawing.Size(267, 255);
+      this.parametersGroupBox.TabIndex = 0;
+      this.parametersGroupBox.TabStop = false;
+      this.parametersGroupBox.Text = "&Parameters";
+      // 
+      // parametersTreeView
+      // 
+      this.parametersTreeView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+      this.parametersTreeView.CheckBoxes = true;
+      this.parametersTreeView.HideSelection = false;
+      this.parametersTreeView.Location = new System.Drawing.Point(6, 19);
+      this.parametersTreeView.Name = "parametersTreeView";
+      this.parametersTreeView.ShowRootLines = false;
+      this.parametersTreeView.Size = new System.Drawing.Size(255, 230);
+      this.parametersTreeView.TabIndex = 0;
+      this.parametersTreeView.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(this.parametersTreeView_AfterCheck);
+      this.parametersTreeView.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.parametersTreeView_AfterSelect);
+      // 
+      // groupsGroupBox
+      // 
+      this.groupsGroupBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+      this.groupsGroupBox.Controls.Add(this.removeGroupButton);
+      this.groupsGroupBox.Controls.Add(this.groupsTreeView);
+      this.groupsGroupBox.Controls.Add(this.addGroupButton);
+      this.groupsGroupBox.Location = new System.Drawing.Point(3, 4);
+      this.groupsGroupBox.Name = "groupsGroupBox";
+      this.groupsGroupBox.Size = new System.Drawing.Size(267, 579);
+      this.groupsGroupBox.TabIndex = 0;
+      this.groupsGroupBox.TabStop = false;
+      this.groupsGroupBox.Text = "&Groups";
+      // 
+      // removeGroupButton
+      // 
+      this.removeGroupButton.Enabled = false;
+      this.removeGroupButton.Image = HeuristicLab.Common.Resources.VSImageLibrary.Remove;
+      this.removeGroupButton.Location = new System.Drawing.Point(36, 19);
+      this.removeGroupButton.Name = "removeGroupButton";
+      this.removeGroupButton.Size = new System.Drawing.Size(24, 24);
+      this.removeGroupButton.TabIndex = 1;
+      this.removeGroupButton.UseVisualStyleBackColor = true;
+      this.removeGroupButton.Click += new System.EventHandler(this.removeGroupButton_Click);
+      // 
+      // addGroupButton
+      // 
+      this.addGroupButton.Enabled = false;
+      this.addGroupButton.Image = HeuristicLab.Common.Resources.VSImageLibrary.Add;
+      this.addGroupButton.Location = new System.Drawing.Point(6, 19);
+      this.addGroupButton.Name = "addGroupButton";
+      this.addGroupButton.Size = new System.Drawing.Size(24, 24);
+      this.addGroupButton.TabIndex = 0;
+      this.addGroupButton.UseVisualStyleBackColor = true;
+      this.addGroupButton.Click += new System.EventHandler(this.addGroupButton_Click);
+      // 
+      // dataRowsGroupBox
+      // 
+      this.dataRowsGroupBox.Anchor = System.Windows.Forms.AnchorStyles.Right;
+      this.dataRowsGroupBox.Controls.Add(this.minMaxCheckBox);
+      this.dataRowsGroupBox.Controls.Add(this.medianCheckBox);
+      this.dataRowsGroupBox.Controls.Add(this.quartilesCheckBox);
+      this.dataRowsGroupBox.Controls.Add(this.averageCheckBox);
+      this.dataRowsGroupBox.Location = new System.Drawing.Point(744, 395);
+      this.dataRowsGroupBox.Name = "dataRowsGroupBox";
+      this.dataRowsGroupBox.Size = new System.Drawing.Size(77, 111);
+      this.dataRowsGroupBox.TabIndex = 4;
+      this.dataRowsGroupBox.TabStop = false;
+      this.dataRowsGroupBox.Text = "&Data Rows";
+      // 
+      // minMaxCheckBox
+      // 
+      this.minMaxCheckBox.AutoSize = true;
+      this.minMaxCheckBox.Location = new System.Drawing.Point(6, 19);
+      this.minMaxCheckBox.Name = "minMaxCheckBox";
+      this.minMaxCheckBox.Size = new System.Drawing.Size(68, 17);
+      this.minMaxCheckBox.TabIndex = 0;
+      this.minMaxCheckBox.Text = "&Min/Max";
+      this.minMaxCheckBox.UseVisualStyleBackColor = true;
+      this.minMaxCheckBox.CheckedChanged += new System.EventHandler(this.dataRowCheckBox_CheckedChanged);
+      // 
+      // medianCheckBox
+      // 
+      this.medianCheckBox.AutoSize = true;
+      this.medianCheckBox.Checked = true;
+      this.medianCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
+      this.medianCheckBox.Location = new System.Drawing.Point(6, 88);
+      this.medianCheckBox.Name = "medianCheckBox";
+      this.medianCheckBox.Size = new System.Drawing.Size(61, 17);
+      this.medianCheckBox.TabIndex = 3;
+      this.medianCheckBox.Text = "M&edian";
+      this.medianCheckBox.UseVisualStyleBackColor = true;
+      this.medianCheckBox.CheckedChanged += new System.EventHandler(this.dataRowCheckBox_CheckedChanged);
+      // 
+      // quartilesCheckBox
+      // 
+      this.quartilesCheckBox.AutoSize = true;
+      this.quartilesCheckBox.Checked = true;
+      this.quartilesCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
+      this.quartilesCheckBox.Location = new System.Drawing.Point(6, 42);
+      this.quartilesCheckBox.Name = "quartilesCheckBox";
+      this.quartilesCheckBox.Size = new System.Drawing.Size(59, 17);
+      this.quartilesCheckBox.TabIndex = 1;
+      this.quartilesCheckBox.Text = "&Q1/Q3";
+      this.quartilesCheckBox.UseVisualStyleBackColor = true;
+      this.quartilesCheckBox.CheckedChanged += new System.EventHandler(this.dataRowCheckBox_CheckedChanged);
+      // 
+      // averageCheckBox
+      // 
+      this.averageCheckBox.AutoSize = true;
+      this.averageCheckBox.Location = new System.Drawing.Point(6, 65);
+      this.averageCheckBox.Name = "averageCheckBox";
+      this.averageCheckBox.Size = new System.Drawing.Size(66, 17);
+      this.averageCheckBox.TabIndex = 2;
+      this.averageCheckBox.Text = "&Average";
+      this.averageCheckBox.UseVisualStyleBackColor = true;
+      this.averageCheckBox.CheckedChanged += new System.EventHandler(this.dataRowCheckBox_CheckedChanged);
+      // 
+      // RunCollectionParameterAnalysisView
+      // 
+      this.BackColor = System.Drawing.SystemColors.Window;
+      this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+      this.Controls.Add(this.splitContainer);
+      this.Name = "RunCollectionParameterAnalysisView";
+      this.Size = new System.Drawing.Size(1101, 851);
+      ((System.ComponentModel.ISupportInitialize)(this.chart)).EndInit();
+      ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).EndInit();
+      this.splitContainer.Panel1.ResumeLayout(false);
+      this.splitContainer.Panel2.ResumeLayout(false);
+      this.splitContainer.Panel2.PerformLayout();
+      ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
+      this.splitContainer.ResumeLayout(false);
+      this.splitContainer1.Panel1.ResumeLayout(false);
+      this.splitContainer1.Panel2.ResumeLayout(false);
+      ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
+      this.splitContainer1.ResumeLayout(false);
+      this.parametersGroupBox.ResumeLayout(false);
+      this.groupsGroupBox.ResumeLayout(false);
+      this.dataRowsGroupBox.ResumeLayout(false);
+      this.dataRowsGroupBox.PerformLayout();
+      this.ResumeLayout(false);
+
+    }
+
+    #endregion
+
+    private Visualization.ChartControlsExtensions.EnhancedChart chart;
+    private System.Windows.Forms.Label stepSizeLabel;
+    private System.Windows.Forms.TextBox stepSizeTextBox;
+    private System.Windows.Forms.ToolTip toolTip;
+    private System.Windows.Forms.ErrorProvider errorProvider;
+    private System.Windows.Forms.CheckBox logScalingCheckBox;
+    private System.Windows.Forms.SplitContainer splitContainer;
+    private System.Windows.Forms.TreeView groupsTreeView;
+    private System.Windows.Forms.GroupBox parametersGroupBox;
+    private System.Windows.Forms.TreeView parametersTreeView;
+    private System.Windows.Forms.CheckBox medianCheckBox;
+    private System.Windows.Forms.CheckBox averageCheckBox;
+    private System.Windows.Forms.CheckBox quartilesCheckBox;
+    private System.Windows.Forms.CheckBox minMaxCheckBox;
+    private System.Windows.Forms.GroupBox dataRowsGroupBox;
+    private System.Windows.Forms.GroupBox groupsGroupBox;
+    private System.Windows.Forms.Button removeGroupButton;
+    private System.Windows.Forms.Button addGroupButton;
+    private System.Windows.Forms.SplitContainer splitContainer1;
+  }
+}
Index: branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionParameterAnalysisView.cs
===================================================================
--- branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionParameterAnalysisView.cs	(revision 14751)
+++ branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionParameterAnalysisView.cs	(revision 14751)
@@ -0,0 +1,636 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Linq;
+using System.Windows.Forms;
+using HeuristicLab.Analysis;
+using HeuristicLab.Collections;
+using HeuristicLab.Common;
+using HeuristicLab.Core.Views;
+using HeuristicLab.MainForm;
+using System.Windows.Forms.DataVisualization.Charting;
+using System.Text;
+using HeuristicLab.Data;
+using System.Drawing;
+using HeuristicLab.Core;
+
+namespace HeuristicLab.Optimization.Views {
+  [View("Parameter Analysis")]
+  [Content(typeof(RunCollection), false)]
+  public sealed partial class RunCollectionParameterAnalysisView : ItemView {
+    #region Colors
+    private static readonly Color[] colors = new[] {
+      Color.FromArgb(0x40, 0x6A, 0xB7),
+      Color.FromArgb(0xB1, 0x6D, 0x01),
+      Color.FromArgb(0x4E, 0x8A, 0x06),
+      Color.FromArgb(0x75, 0x50, 0x7B),
+      Color.FromArgb(0x72, 0x9F, 0xCF),
+      Color.FromArgb(0xA4, 0x00, 0x00),
+      Color.FromArgb(0xAD, 0x7F, 0xA8),
+      Color.FromArgb(0x29, 0x50, 0xCF),
+      Color.FromArgb(0x90, 0xB0, 0x60),
+      Color.FromArgb(0xF5, 0x89, 0x30),
+      Color.FromArgb(0x55, 0x57, 0x53),
+      Color.FromArgb(0xEF, 0x59, 0x59),
+      Color.FromArgb(0xED, 0xD4, 0x30),
+      Color.FromArgb(0x63, 0xC2, 0x16),
+    };
+    #endregion
+
+    private bool suppressUpdates = false;
+    private int stepSize = 1000;
+    private Dictionary<IRun, List<Tuple<int, double>>> runData;
+    private Dictionary<string, ParameterInfo> paramInfos;
+
+    public new RunCollection Content {
+      get { return (RunCollection)base.Content; }
+      set { base.Content = value; }
+    }
+
+    public RunCollectionParameterAnalysisView() {
+      InitializeComponent();
+      chart.CustomizeAllChartAreas();
+      stepSizeTextBox.Text = stepSize.ToString();
+      errorProvider.SetIconAlignment(stepSizeTextBox, ErrorIconAlignment.MiddleLeft);
+      errorProvider.SetIconPadding(stepSizeTextBox, 2);
+    }
+
+    #region Content Events
+    protected override void DeregisterContentEvents() {
+      Content.ItemsAdded -= Content_ItemsAdded;
+      Content.ItemsRemoved -= Content_ItemsRemoved;
+      Content.CollectionReset -= Content_CollectionReset;
+      Content.UpdateOfRunsInProgressChanged -= Content_UpdateOfRunsInProgressChanged;
+      DeregisterRunEvents(Content);
+      base.DeregisterContentEvents();
+    }
+    protected override void RegisterContentEvents() {
+      base.RegisterContentEvents();
+      Content.ItemsAdded += Content_ItemsAdded;
+      Content.ItemsRemoved += Content_ItemsRemoved;
+      Content.CollectionReset += Content_CollectionReset;
+      Content.UpdateOfRunsInProgressChanged += Content_UpdateOfRunsInProgressChanged;
+      RegisterRunEvents(Content);
+    }
+    private void DeregisterRunEvents(IEnumerable<IRun> runs) {
+      foreach (var run in runs)
+        run.PropertyChanged -= Run_PropertyChanged;
+    }
+    private void RegisterRunEvents(IEnumerable<IRun> runs) {
+      foreach (var run in runs)
+        run.PropertyChanged += Run_PropertyChanged;
+    }
+
+    private void Content_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IRun> e) {
+      RegisterRunEvents(e.Items);
+    }
+    private void Content_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IRun> e) {
+      DeregisterRunEvents(e.Items);
+    }
+    private void Content_CollectionReset(object sender, CollectionItemsChangedEventArgs<IRun> e) {
+      DeregisterRunEvents(e.OldItems);
+      RegisterRunEvents(e.Items);
+    }
+    private void Content_UpdateOfRunsInProgressChanged(object sender, EventArgs e) {
+      if (InvokeRequired)
+        Invoke((Action<object, EventArgs>)Content_UpdateOfRunsInProgressChanged, sender, e);
+      else {
+        suppressUpdates = Content.UpdateOfRunsInProgress;
+        if (suppressUpdates) return;
+        CollectRunData();
+        AnalyzeParameters();
+        UpdateChart();
+      }
+    }
+    private void Run_PropertyChanged(object sender, PropertyChangedEventArgs e) {
+      if (suppressUpdates) return;
+      if (InvokeRequired)
+        Invoke((Action<object, PropertyChangedEventArgs>)Run_PropertyChanged, sender, e);
+      else {
+        CollectRunData();
+        AnalyzeParameters();
+        UpdateChart();
+      }
+    }
+    #endregion
+
+    protected override void OnContentChanged() {
+      base.OnContentChanged();
+      CollectRunData();
+      AnalyzeParameters();
+      UpdateChart();
+    }
+    protected override void SetEnabledStateOfControls() {
+      base.SetEnabledStateOfControls();
+      parametersGroupBox.Enabled = Content != null;
+      groupsGroupBox.Enabled = Content != null;
+      stepSizeTextBox.Enabled = Content != null;
+      logScalingCheckBox.Enabled = Content != null;
+      dataRowsGroupBox.Enabled = Content != null;
+    }
+
+    private void AnalyzeParameters() {
+      paramInfos = new Dictionary<string, ParameterInfo>();
+      if (Content == null) return;
+
+      foreach (var run in runData.Keys) {
+        foreach (var param in run.Parameters) {
+          ParameterInfo info;
+          if (!paramInfos.TryGetValue(param.Key, out info))
+            paramInfos.Add(param.Key, new ParameterInfo(param.Key, param.Value.ToString(), run));
+          else
+            info.AddValue(param.Value.ToString(), run);
+        }
+      }
+
+      // remove irrelevant parameters
+      if (paramInfos.ContainsKey("Seed"))
+        paramInfos.Remove("Seed");
+
+      // remove all parameters which only have a single value
+      var singles = new List<string>();
+      foreach (var paramInfo in paramInfos.Values) {
+        if (paramInfo.Values.Count == 1)
+          singles.Add(paramInfo.Name);
+      }
+      foreach (var single in singles)
+        paramInfos.Remove(single);
+
+      // set color of parameter values
+      int i = 0;
+      foreach (var valueInfo in paramInfos.Values.SelectMany(x => x.Values.Values)) {
+        valueInfo.Color = colors[i];
+        i = (i + 1) % colors.Length;
+      }
+
+      // populate parametersTreeView
+      parametersTreeView.Nodes.Clear();
+      var paramsRoot = new TreeNode("All Runs (" + runData.Keys.Count + ")");
+      paramsRoot.Tag = runData.Keys.ToList();
+      foreach (var paramInfo in paramInfos.Values.OrderBy(x => x.Name)) {
+        var node = new TreeNode(paramInfo.Name + " (" + paramInfo.RunCount + ")");
+        foreach (var value in paramInfo.Values) {
+          var child = new TreeNode(value.Key + " (" + value.Value.RunCount + ")");
+          child.Tag = value.Value;
+          node.Nodes.Add(child);
+        }
+        node.Tag = paramInfo;
+        paramsRoot.Nodes.Add(node);
+      }
+      parametersTreeView.Nodes.Add(paramsRoot);
+      paramsRoot.Expand();
+      paramsRoot.Checked = true;
+      parametersTreeView.SelectedNode = paramsRoot;
+
+      // populate groupsTreeView
+      groupsTreeView.Nodes.Clear();
+      var groupsRoot = new TreeNode("All Runs (" + runData.Keys.Count + ")");
+      groupsRoot.Tag = runData.Keys.ToList();
+      groupsTreeView.Nodes.Add(groupsRoot);
+      groupsRoot.Expand();
+      groupsTreeView.SelectedNode = groupsRoot;
+    }
+    private void CollectRunData() {
+      runData = new Dictionary<IRun, List<Tuple<int, double>>>();
+      if (Content == null) return;
+
+      foreach (var run in Content) {
+        try {
+          IList<Tuple<double, double>> values = (run.Results["QualityPerEvaluations"] as IndexedDataTable<double>).Rows["First-hit Graph"].Values;
+          var bestKnown = (run.Results["BestKnownQuality"] as DoubleValue).Value;
+
+          var data = new List<Tuple<int, double>>();
+          int i = 0;
+          double qual = double.NaN;
+          foreach (var val in values) {
+            while (i * stepSize < val.Item1) {
+              if (!double.IsNaN(qual)) data.Add(Tuple.Create(i * stepSize, qual));
+              i++;
+            }
+            var diff = Math.Abs(bestKnown - val.Item2);
+            qual = bestKnown == 0 ? diff : diff / bestKnown;
+          }
+          runData.Add(run, data);
+        }
+        catch {
+        }
+      }
+    }
+
+    private void UpdateChart() {
+      if (suppressUpdates) return;
+
+      chart.Series.Clear();
+      chart.Legends[0].CustomItems.Clear();
+      chart.Titles[0].Text = string.Empty;
+
+      var checkedNodes = TraverseTreeNodes(parametersTreeView.Nodes)
+                           .Where(x => x.Checked)
+                           .Concat(
+                             TraverseTreeNodes(groupsTreeView.Nodes)
+                               .Where(x => x.Checked)
+                           );
+
+      foreach (var node in checkedNodes) {
+        if (node.Parent == null) {
+          var series = BuildSeries(node.Tag as IEnumerable<IRun>, colors[0]);
+          chart.Titles[0].Text = node.Text;
+          foreach (var s in series)
+            chart.Series.Add(s);
+          var legendItem = new LegendItem();
+          var legendItemInfo = new LegendItemInfo(colors[0], series);
+          legendItem.Color = legendItemInfo.Color;
+          legendItem.BorderColor = Color.Transparent;
+          legendItem.Name = node.Text;
+          legendItem.Tag = legendItemInfo;
+          chart.Legends[0].CustomItems.Add(legendItem);
+        } else if (node.Tag is ParameterInfo) {
+          var paramInfo = node.Tag as ParameterInfo;
+          chart.Titles[0].Text = paramInfo.Name + " (" + paramInfo.RunCount + ")";
+          foreach (var value in paramInfo.Values) {
+            var series = BuildSeries(value.Value.Runs, value.Value.Color);
+            foreach (var s in series)
+              chart.Series.Add(s);
+            var legendItem = new LegendItem();
+            var legendItemInfo = new LegendItemInfo(value.Value.Color, series);
+            legendItem.Color = legendItemInfo.Color;
+            legendItem.BorderColor = Color.Transparent;
+            legendItem.Name = value.Key + " (" + value.Value.RunCount + ")";
+            legendItem.Tag = legendItemInfo;
+            chart.Legends[0].CustomItems.Add(legendItem);
+          }
+        } else if (node.Tag is ParameterValueInfo) {
+          var valueInfo = node.Tag as ParameterValueInfo;
+          var series = BuildSeries(valueInfo.Runs, valueInfo.Color);
+          chart.Titles[0].Text = node.Parent.Text;
+          foreach (var s in series)
+            chart.Series.Add(s);
+          var legendItem = new LegendItem();
+          var legendItemInfo = new LegendItemInfo(valueInfo.Color, series);
+          legendItem.Color = legendItemInfo.Color;
+          legendItem.BorderColor = Color.Transparent;
+          legendItem.Name = node.Text;
+          legendItem.Tag = legendItemInfo;
+          chart.Legends[0].CustomItems.Add(legendItem);
+        } else if (node.Tag is GroupInfo) {
+          var groupInfo = node.Tag as GroupInfo;
+          if (groupInfo.IsParameter) {
+            chart.Titles[0].Text = groupInfo.Text + " (" + groupInfo.Runs.Count + ")";
+            foreach (TreeNode child in node.Nodes) {
+              var childInfo = child.Tag as GroupInfo;
+              var series = BuildSeries(childInfo.Runs, childInfo.Color);
+              foreach (var s in series)
+                chart.Series.Add(s);
+              var legendItem = new LegendItem();
+              var legendItemInfo = new LegendItemInfo(childInfo.Color, series);
+              legendItem.Color = legendItemInfo.Color;
+              legendItem.BorderColor = Color.Transparent;
+              legendItem.Name = childInfo.Text + " (" + childInfo.Runs.Count + ")";
+              legendItem.Tag = legendItemInfo;
+              chart.Legends[0].CustomItems.Add(legendItem);
+            }
+          } else {
+            var parentInfo = node.Parent.Tag as GroupInfo;
+            chart.Titles[0].Text = parentInfo.Text + " (" + parentInfo.Runs.Count + ")";
+            var series = BuildSeries(groupInfo.Runs, groupInfo.Color);
+            foreach (var s in series)
+              chart.Series.Add(s);
+            var legendItem = new LegendItem();
+            var legendItemInfo = new LegendItemInfo(groupInfo.Color, series);
+            legendItem.Color = legendItemInfo.Color;
+            legendItem.BorderColor = Color.Transparent;
+            legendItem.Name = groupInfo.Text + " (" + groupInfo.Runs.Count + ")";
+            legendItem.Tag = legendItemInfo;
+            chart.Legends[0].CustomItems.Add(legendItem);
+          }
+        }
+      }
+    }
+    private void UpdateSeriesVisibility() {
+      foreach (var legendItem in chart.Legends[0].CustomItems) {
+        var legendItemInfo = legendItem.Tag as LegendItemInfo;
+        foreach (var s in legendItemInfo.Series) {
+          if (legendItemInfo.SeriesVisible) {
+            var seriesInfo = s.Tag as SeriesInfo;
+            switch (seriesInfo.Type) {
+              case SeriesTypes.MinMax:
+                s.Color = minMaxCheckBox.Checked ? seriesInfo.Color : Color.Transparent;
+                break;
+              case SeriesTypes.Quartiles:
+                s.Color = quartilesCheckBox.Checked ? seriesInfo.Color : Color.Transparent;
+                break;
+              case SeriesTypes.Median:
+                s.Color = medianCheckBox.Checked ? seriesInfo.Color : Color.Transparent;
+                break;
+              case SeriesTypes.Average:
+                s.Color = averageCheckBox.Checked ? seriesInfo.Color : Color.Transparent;
+                break;
+            }
+          } else {
+            s.Color = Color.Transparent;
+          }
+        }
+      }
+    }
+
+    private IEnumerable<Series> BuildSeries(IEnumerable<IRun> runs, Color color) {
+      var values = new Dictionary<int, List<double>>();
+      foreach (var run in runs) {
+        foreach (var step in runData[run]) {
+          List<double> vals;
+          if (!values.TryGetValue(step.Item1, out vals)) {
+            vals = new List<double>();
+            values.Add(step.Item1, vals);
+          }
+          vals.Add(step.Item2);
+        }
+      }
+
+      List<Series> series = new List<Series>();
+      var minMaxSeries = new Series();
+      var minMaxSeriesInfo = new SeriesInfo(Color.FromArgb(25, color), SeriesTypes.MinMax);
+      minMaxSeries.ChartType = SeriesChartType.Range;
+      minMaxSeries.Color = minMaxCheckBox.Checked ? minMaxSeriesInfo.Color : Color.Transparent;
+      minMaxSeries.IsVisibleInLegend = false;
+      minMaxSeries.Tag = minMaxSeriesInfo;
+      series.Add(minMaxSeries);
+
+      var quartilesSeries = new Series();
+      var quartilesSeriesInfo = new SeriesInfo(Color.FromArgb(50, color), SeriesTypes.Quartiles);
+      quartilesSeries.ChartType = SeriesChartType.Range;
+      quartilesSeries.Color = quartilesCheckBox.Checked ? quartilesSeriesInfo.Color : Color.Transparent;
+      quartilesSeries.IsVisibleInLegend = false;
+      quartilesSeries.Tag = quartilesSeriesInfo;
+      series.Add(quartilesSeries);
+
+      var medianSeries = new Series();
+      var medianSeriesInfo = new SeriesInfo(color, SeriesTypes.Median);
+      medianSeries.ChartType = SeriesChartType.FastLine;
+      medianSeries.Color = medianCheckBox.Checked ? medianSeriesInfo.Color : Color.Transparent;
+      medianSeries.BorderWidth = 3;
+      medianSeries.BorderDashStyle = ChartDashStyle.Solid;
+      medianSeries.IsVisibleInLegend = false;
+      medianSeries.Tag = medianSeriesInfo;
+      series.Add(medianSeries);
+
+      var averageSeries = new Series();
+      var averageSeriesInfo = new SeriesInfo(color, SeriesTypes.Average);
+      averageSeries.ChartType = SeriesChartType.FastLine;
+      averageSeries.Color = averageCheckBox.Checked ? averageSeriesInfo.Color : Color.Transparent;
+      averageSeries.BorderWidth = 3;
+      averageSeries.BorderDashStyle = ChartDashStyle.Dash;
+      averageSeries.IsVisibleInLegend = false;
+      averageSeries.Tag = averageSeriesInfo;
+      series.Add(averageSeries);
+
+      foreach (var point in values.OrderBy(x => x.Key)) {
+        if (point.Value.Count > 0) {
+          minMaxSeries.Points.Add(new DataPoint(point.Key, new double[] { point.Value.Min(), point.Value.Max() }));
+          quartilesSeries.Points.Add(new DataPoint(point.Key, new double[] { point.Value.Quantile(0.25), point.Value.Quantile(0.75) }));
+          medianSeries.Points.Add(new DataPoint(point.Key, point.Value.Median()));
+          averageSeries.Points.Add(new DataPoint(point.Key, point.Value.Average()));
+        }
+      }
+      return series;
+    }
+
+    #region Control Events
+    #region stepSizeTextBox
+    private void stepSizeTextBox_KeyDown(object sender, KeyEventArgs e) {
+      if ((e.KeyCode == Keys.Enter) || (e.KeyCode == Keys.Return))
+        stepSizeLabel.Select();  // select label to validate data
+
+      if (e.KeyCode == Keys.Escape) {
+        stepSizeTextBox.Text = stepSize.ToString();
+        stepSizeLabel.Select();  // select label to validate data
+      }
+    }
+    private void stepSizeTextBox_Validating(object sender, CancelEventArgs e) {
+      int val;
+      if (!int.TryParse(stepSizeTextBox.Text, out val) || val <= 0) {
+        e.Cancel = true;
+        errorProvider.SetError(stepSizeTextBox, "Invalid Value (Valid Value Format: \"[+]digits\")");
+        stepSizeTextBox.SelectAll();
+      }
+    }
+    private void stepSizeTextBox_Validated(object sender, EventArgs e) {
+      int val = int.Parse(stepSizeTextBox.Text);
+      errorProvider.SetError(stepSizeTextBox, string.Empty);
+      stepSizeTextBox.Text = val.ToString();
+      if (stepSize != val) {
+        stepSize = val;
+        CollectRunData();
+        UpdateChart();
+      }
+    }
+    #endregion
+    #region logScalingCheckBox
+    private void logScalingCheckBox_CheckedChanged(object sender, EventArgs e) {
+      chart.ChartAreas[0].AxisX.IsLogarithmic = logScalingCheckBox.Checked;
+    }
+    #endregion
+    #region chart
+    private void chart_MouseDown(object sender, MouseEventArgs e) {
+      HitTestResult result = chart.HitTest(e.X, e.Y);
+      if (result.ChartElementType == ChartElementType.LegendItem) {
+        var legendItemInfo = (result.Object as LegendItem).Tag as LegendItemInfo;
+        legendItemInfo.SeriesVisible = !legendItemInfo.SeriesVisible;
+        UpdateSeriesVisibility();
+      }
+    }
+    private void chart_MouseMove(object sender, MouseEventArgs e) {
+      HitTestResult result = chart.HitTest(e.X, e.Y);
+      switch (result.ChartElementType) {
+        case ChartElementType.LegendItem:
+          Cursor = Cursors.Hand;
+          break;
+        default:
+          Cursor = Cursors.Default;
+          break;
+      }
+    }
+    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
+      foreach (LegendItem legendItem in e.LegendItems) {
+        var legendItemInfo = legendItem.Tag as LegendItemInfo;
+        legendItem.Color = legendItemInfo.SeriesVisible ? legendItemInfo.Color : Color.Transparent;
+        foreach (LegendCell cell in legendItem.Cells) {
+          cell.ForeColor = legendItemInfo.SeriesVisible ? Color.Black : Color.Gray;
+        }
+      }
+    }
+    #endregion
+    #region parametersTreeView
+    private void parametersTreeView_AfterSelect(object sender, TreeViewEventArgs e) {
+      addGroupButton.Enabled = (parametersTreeView.SelectedNode != null) && (parametersTreeView.SelectedNode.Parent != null);
+    }
+    private void parametersTreeView_AfterCheck(object sender, TreeViewEventArgs e) {
+      UpdateChart();
+    }
+    #endregion
+    #region groupsTreeView
+    private void groupsTreeView_AfterSelect(object sender, TreeViewEventArgs e) {
+      removeGroupButton.Enabled = (groupsTreeView.SelectedNode != null) && (groupsTreeView.SelectedNode.Parent != null);
+    }
+    private void groupsTreeView_AfterCheck(object sender, TreeViewEventArgs e) {
+      UpdateChart();
+    }
+    #endregion
+    #region addGroupButton, removeGroupButton
+    private void addGroupButton_Click(object sender, EventArgs e) {
+      var group = groupsTreeView.SelectedNode;
+      var param = parametersTreeView.SelectedNode;
+      var groupRuns = group.Parent == null ? group.Tag as IEnumerable<IRun> : (group.Tag as GroupInfo).Runs;
+
+      if (param.Tag is ParameterInfo) {
+        var paramInfo = param.Tag as ParameterInfo;
+        var paramNode = new TreeNode();
+        foreach (var valueInfo in paramInfo.Values.Values) {
+          var valueRuns = groupRuns.Intersect(valueInfo.Runs);
+          var valueNode = new TreeNode(valueInfo.Value + " (" + valueRuns.Count() + ")");
+          valueNode.Tag = new GroupInfo(valueInfo.Value, valueInfo.Color, valueRuns, false);
+          paramNode.Nodes.Add(valueNode);
+        }
+        var paramRuns = groupRuns.Intersect(paramInfo.Runs);
+        paramNode.Text = paramInfo.Name + " (" + paramRuns.Count() + ")";
+        paramNode.Tag = new GroupInfo(paramInfo.Name, Color.Empty, paramRuns, true);
+        group.Nodes.Add(paramNode);
+      } else if (param.Tag is ParameterValueInfo) {
+        var paramInfo = param.Parent.Tag as ParameterInfo;
+        var valueInfo = param.Tag as ParameterValueInfo;
+        var valueRuns = groupRuns.Intersect(valueInfo.Runs);
+        var paramRuns = groupRuns.Intersect(paramInfo.Runs);
+        var paramNode = new TreeNode(paramInfo.Name + " (" + paramRuns.Count() + ")");
+        var valueNode = new TreeNode(valueInfo.Value + " (" + valueRuns.Count() + ")");
+        valueNode.Tag = new GroupInfo(valueInfo.Value, valueInfo.Color, valueRuns, false);
+        paramNode.Tag = new GroupInfo(paramInfo.Name, Color.Empty, paramRuns, true);
+        paramNode.Nodes.Add(valueNode);
+        group.Nodes.Add(paramNode);
+      }
+      groupsTreeView.Nodes[0].Expand();
+    }
+    private void removeGroupButton_Click(object sender, EventArgs e) {
+      if (groupsTreeView.SelectedNode != null)
+        groupsTreeView.SelectedNode.Remove();
+      UpdateChart();
+    }
+    #endregion
+    #region minMaxCheckBox, quartilesCheckBox, averageCheckBox, medianCheckBox
+    private void dataRowCheckBox_CheckedChanged(object sender, EventArgs e) {
+      UpdateSeriesVisibility();
+    }
+    #endregion
+    #endregion
+
+    #region Helpers
+    private IEnumerable<TreeNode> TraverseTreeNodes(TreeNodeCollection nodes) {
+      foreach (var node in nodes.OfType<TreeNode>()) {
+        yield return node;
+        foreach (var child in TraverseTreeNodes(node.Nodes))
+          yield return child;
+      }
+    }
+    #endregion
+
+    #region Inner Types
+    class ParameterInfo {
+      public string Name { get; private set; }
+      public Dictionary<string, ParameterValueInfo> Values { get; private set; }
+      public IEnumerable<IRun> Runs {
+        get { return Values.Values.SelectMany(x => x.Runs); }
+      }
+      public int RunCount {
+        get { return Values.Values.Select(x => x.RunCount).Sum(); }
+      }
+
+      public ParameterInfo(string name, string value, IRun run) {
+        Name = name;
+        Values = new Dictionary<string, ParameterValueInfo>();
+        AddValue(value, run);
+      }
+      public void AddValue(string value, IRun run) {
+        ParameterValueInfo valueInfo;
+        if (!Values.TryGetValue(value, out valueInfo)) {
+          Values.Add(value, new ParameterValueInfo(value, run));
+        } else {
+          valueInfo.Runs.Add(run);
+        }
+      }
+    }
+    class ParameterValueInfo {
+      public string Value { get; private set; }
+      public Color Color { get; set; }
+      public IList<IRun> Runs { get; private set; }
+      public int RunCount {
+        get { return Runs.Count; }
+      }
+
+      public ParameterValueInfo(string value, IRun run) {
+        Value = value;
+        Runs = new List<IRun>();
+        Runs.Add(run);
+      }
+      public ParameterValueInfo(string value, Color color, IRun run) : this(value, run) {
+        Color = color;
+      }
+    }
+    class GroupInfo {
+      public string Text { get; private set; }
+      public Color Color { get; private set; }
+      public IList<IRun> Runs { get; private set; }
+      public bool IsParameter { get; private set; }
+      public GroupInfo(string text, Color color, IEnumerable<IRun> runs, bool isParamter) {
+        Text = text;
+        Color = color;
+        Runs = runs.ToList();
+        IsParameter = isParamter;
+      }
+    }
+    enum SeriesTypes {
+      MinMax,
+      Quartiles,
+      Average,
+      Median
+    }
+    class SeriesInfo {
+      public Color Color { get; private set; }
+      public SeriesTypes Type { get; private set; }
+      public SeriesInfo(Color color, SeriesTypes type) {
+        Color = color;
+        Type = type;
+      }
+    }
+    class LegendItemInfo {
+      public Color Color { get; private set; }
+      public bool SeriesVisible { get; set; }
+      public IEnumerable<Series> Series { get; private set; }
+      public LegendItemInfo(Color color, IEnumerable<Series> series) {
+        Color = color;
+        SeriesVisible = true;
+        Series = series;
+      }
+    }
+    #endregion
+  }
+}
Index: branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.Designer.cs
===================================================================
--- branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.Designer.cs	(revision 14232)
+++ branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.Designer.cs	(revision 14751)
@@ -64,10 +64,12 @@
       this.tabControl = new System.Windows.Forms.TabControl();
       this.byTargetTabPage = new System.Windows.Forms.TabPage();
+      this.relativeOrAbsoluteComboBox = new System.Windows.Forms.ComboBox();
       this.targetChart = new HeuristicLab.Visualization.ChartControlsExtensions.EnhancedChart();
+      this.markerCheckBox = new System.Windows.Forms.CheckBox();
       this.boundShadingCheckBox = new System.Windows.Forms.CheckBox();
       this.byCostTabPage = new System.Windows.Forms.TabPage();
       this.byCostViewHost = new HeuristicLab.MainForm.WindowsForms.ViewHost();
       this.budgetLogScalingCheckBox = new System.Windows.Forms.CheckBox();
-      this.eachOrAllBudgetsCheckBox = new System.Windows.Forms.CheckBox();
+      this.aggregateBudgetsCheckBox = new System.Windows.Forms.CheckBox();
       this.generateBudgetsButton = new System.Windows.Forms.Button();
       this.byTableTabPage = new System.Windows.Forms.TabPage();
@@ -143,5 +145,5 @@
       this.targetsTextBox.Location = new System.Drawing.Point(59, 8);
       this.targetsTextBox.Name = "targetsTextBox";
-      this.targetsTextBox.Size = new System.Drawing.Size(287, 20);
+      this.targetsTextBox.Size = new System.Drawing.Size(204, 20);
       this.targetsTextBox.TabIndex = 1;
       this.toolTip.SetToolTip(this.targetsTextBox, "The order of the targets is important, first to-hit targets\r\nshould be given firs" +
@@ -200,5 +202,5 @@
       this.budgetsTextBox.Location = new System.Drawing.Point(59, 8);
       this.budgetsTextBox.Name = "budgetsTextBox";
-      this.budgetsTextBox.Size = new System.Drawing.Size(326, 20);
+      this.budgetsTextBox.Size = new System.Drawing.Size(309, 20);
       this.budgetsTextBox.TabIndex = 6;
       this.budgetsTextBox.Validating += new System.ComponentModel.CancelEventHandler(this.budgetsTextBox_Validating);
@@ -221,5 +223,5 @@
       this.aggregateTargetsCheckBox.Checked = true;
       this.aggregateTargetsCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
-      this.aggregateTargetsCheckBox.Location = new System.Drawing.Point(352, 10);
+      this.aggregateTargetsCheckBox.Location = new System.Drawing.Point(374, 10);
       this.aggregateTargetsCheckBox.Name = "aggregateTargetsCheckBox";
       this.aggregateTargetsCheckBox.Size = new System.Drawing.Size(74, 17);
@@ -245,5 +247,7 @@
       // byTargetTabPage
       // 
+      this.byTargetTabPage.Controls.Add(this.relativeOrAbsoluteComboBox);
       this.byTargetTabPage.Controls.Add(this.targetChart);
+      this.byTargetTabPage.Controls.Add(this.markerCheckBox);
       this.byTargetTabPage.Controls.Add(this.boundShadingCheckBox);
       this.byTargetTabPage.Controls.Add(this.targetLogScalingCheckBox);
@@ -260,4 +264,18 @@
       this.byTargetTabPage.Text = "Performance by Target";
       this.byTargetTabPage.UseVisualStyleBackColor = true;
+      // 
+      // relativeOrAbsoluteComboBox
+      // 
+      this.relativeOrAbsoluteComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+      this.relativeOrAbsoluteComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+      this.relativeOrAbsoluteComboBox.FormattingEnabled = true;
+      this.relativeOrAbsoluteComboBox.Items.AddRange(new object[] {
+            "relative",
+            "absolute"});
+      this.relativeOrAbsoluteComboBox.Location = new System.Drawing.Point(269, 8);
+      this.relativeOrAbsoluteComboBox.Name = "relativeOrAbsoluteComboBox";
+      this.relativeOrAbsoluteComboBox.Size = new System.Drawing.Size(99, 21);
+      this.relativeOrAbsoluteComboBox.TabIndex = 8;
+      this.relativeOrAbsoluteComboBox.SelectedIndexChanged += new System.EventHandler(this.relativeOrAbsoluteComboBox_SelectedIndexChanged);
       // 
       // targetChart
@@ -291,4 +309,16 @@
       this.targetChart.MouseMove += new System.Windows.Forms.MouseEventHandler(this.chart_MouseMove);
       // 
+      // markerCheckBox
+      // 
+      this.markerCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+      this.markerCheckBox.AutoSize = true;
+      this.markerCheckBox.Location = new System.Drawing.Point(229, 364);
+      this.markerCheckBox.Name = "markerCheckBox";
+      this.markerCheckBox.Size = new System.Drawing.Size(91, 17);
+      this.markerCheckBox.TabIndex = 6;
+      this.markerCheckBox.Text = "show markers";
+      this.markerCheckBox.UseVisualStyleBackColor = true;
+      this.markerCheckBox.CheckedChanged += new System.EventHandler(this.markerCheckBox_CheckedChanged);
+      // 
       // boundShadingCheckBox
       // 
@@ -309,5 +339,5 @@
       this.byCostTabPage.Controls.Add(this.byCostViewHost);
       this.byCostTabPage.Controls.Add(this.budgetLogScalingCheckBox);
-      this.byCostTabPage.Controls.Add(this.eachOrAllBudgetsCheckBox);
+      this.byCostTabPage.Controls.Add(this.aggregateBudgetsCheckBox);
       this.byCostTabPage.Controls.Add(this.generateBudgetsButton);
       this.byCostTabPage.Controls.Add(this.budgetsLabel);
@@ -350,15 +380,17 @@
       this.budgetLogScalingCheckBox.CheckedChanged += new System.EventHandler(this.logScalingCheckBox_CheckedChanged);
       // 
-      // eachOrAllBudgetsCheckBox
-      // 
-      this.eachOrAllBudgetsCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-      this.eachOrAllBudgetsCheckBox.AutoSize = true;
-      this.eachOrAllBudgetsCheckBox.Location = new System.Drawing.Point(414, 10);
-      this.eachOrAllBudgetsCheckBox.Name = "eachOrAllBudgetsCheckBox";
-      this.eachOrAllBudgetsCheckBox.Size = new System.Drawing.Size(36, 17);
-      this.eachOrAllBudgetsCheckBox.TabIndex = 10;
-      this.eachOrAllBudgetsCheckBox.Text = "all";
-      this.eachOrAllBudgetsCheckBox.UseVisualStyleBackColor = true;
-      this.eachOrAllBudgetsCheckBox.CheckedChanged += new System.EventHandler(this.eachOrAllBudgetsCheckBox_CheckedChanged);
+      // aggregateBudgetsCheckBox
+      // 
+      this.aggregateBudgetsCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+      this.aggregateBudgetsCheckBox.AutoSize = true;
+      this.aggregateBudgetsCheckBox.Checked = true;
+      this.aggregateBudgetsCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
+      this.aggregateBudgetsCheckBox.Location = new System.Drawing.Point(374, 10);
+      this.aggregateBudgetsCheckBox.Name = "aggregateBudgetsCheckBox";
+      this.aggregateBudgetsCheckBox.Size = new System.Drawing.Size(74, 17);
+      this.aggregateBudgetsCheckBox.TabIndex = 10;
+      this.aggregateBudgetsCheckBox.Text = "aggregate";
+      this.aggregateBudgetsCheckBox.UseVisualStyleBackColor = true;
+      this.aggregateBudgetsCheckBox.CheckedChanged += new System.EventHandler(this.aggregateBudgetsCheckBox_CheckedChanged);
       // 
       // generateBudgetsButton
@@ -464,5 +496,5 @@
     private System.Windows.Forms.TabPage byCostTabPage;
     private System.Windows.Forms.TabPage byTableTabPage;
-    private System.Windows.Forms.CheckBox eachOrAllBudgetsCheckBox;
+    private System.Windows.Forms.CheckBox aggregateBudgetsCheckBox;
     private System.Windows.Forms.Button generateBudgetsButton;
     private System.Windows.Forms.CheckBox budgetLogScalingCheckBox;
@@ -473,4 +505,6 @@
     private MainForm.WindowsForms.ViewHost byCostViewHost;
     private System.Windows.Forms.CheckBox boundShadingCheckBox;
+    private System.Windows.Forms.CheckBox markerCheckBox;
+    private System.Windows.Forms.ComboBox relativeOrAbsoluteComboBox;
   }
 }
Index: branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.cs
===================================================================
--- branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.cs	(revision 14232)
+++ branches/symbreg-factors-2650/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.cs	(revision 14751)
@@ -30,4 +30,5 @@
 using HeuristicLab.Analysis;
 using HeuristicLab.Collections;
+using HeuristicLab.Core;
 using HeuristicLab.Core.Views;
 using HeuristicLab.Data;
@@ -79,4 +80,6 @@
     private double[] targets;
     private double[] budgets;
+    private bool targetsAreRelative = true;
+    private readonly BindingList<ProblemInstance> problems;
 
     private bool suppressUpdates;
@@ -103,4 +106,9 @@
       };
       byCostViewHost.Content = byCostDataTable;
+      suppressUpdates = true;
+      relativeOrAbsoluteComboBox.SelectedItem = targetsAreRelative ? "relative" : "absolute";
+      problems = new BindingList<ProblemInstance>();
+      problemComboBox.DataSource = new BindingSource() { DataSource = problems };
+      problemComboBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
       suppressUpdates = false;
     }
@@ -223,27 +231,25 @@
       }
 
-      var problems = new HashSet<ProblemDescription>();
-      foreach (var run in Content) {
-        problems.Add(new ProblemDescription(run));
-      }
-
-      var problemTypesDifferent = problems.Select(x => x.ProblemType).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
-      var problemNamesDifferent = problems.Select(x => x.ProblemName).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
-      var evaluatorDifferent = problems.Select(x => x.Evaluator).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
-      var maximizationDifferent = problems.Select(x => x.Maximization).Distinct().Count() > 1;
+      var table = (string)dataTableComboBox.SelectedItem;
+      var problemDict = CalculateBestTargetPerProblemInstance(table);
+
+      var problemTypesDifferent = problemDict.Keys.Select(x => x.ProblemType).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
+      var problemNamesDifferent = problemDict.Keys.Select(x => x.ProblemName).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
+      var evaluatorDifferent = problemDict.Keys.Select(x => x.Evaluator).Where(x => !string.IsNullOrEmpty(x)).Distinct().Count() > 1;
+      var maximizationDifferent = problemDict.Keys.Select(x => x.Maximization).Distinct().Count() > 1;
       var allEqual = !problemTypesDifferent && !problemNamesDifferent && !evaluatorDifferent && !maximizationDifferent;
 
-      var selectedProblemItem = (ProblemDescription)problemComboBox.SelectedItem;
-      problemComboBox.Items.Clear();
-      problemComboBox.Items.Add(ProblemDescription.MatchAll);
-      if (selectedProblemItem == null || selectedProblemItem == ProblemDescription.MatchAll)
-        problemComboBox.SelectedIndex = 0;
-      foreach (var prob in problems.OrderBy(x => x.ToString()).ToList()) {
-        prob.DisplayProblemType = problemTypesDifferent;
-        prob.DisplayProblemName = problemNamesDifferent || allEqual;
-        prob.DisplayEvaluator = evaluatorDifferent;
-        prob.DisplayMaximization = maximizationDifferent;
-        problemComboBox.Items.Add(prob);
-        if (prob.Equals(selectedProblemItem)) problemComboBox.SelectedItem = prob;
+      var selectedProblemItem = (ProblemInstance)problemComboBox.SelectedItem;
+      problems.Clear();
+      problems.Add(ProblemInstance.MatchAll);
+      if (problems[0].Equals(selectedProblemItem)) problemComboBox.SelectedItem = problems[0];
+      foreach (var p in problemDict.ToList()) {
+        p.Key.BestKnownQuality = p.Value;
+        p.Key.DisplayProblemType = problemTypesDifferent;
+        p.Key.DisplayProblemName = problemNamesDifferent || allEqual;
+        p.Key.DisplayEvaluator = evaluatorDifferent;
+        p.Key.DisplayMaximization = maximizationDifferent;
+        problems.Add(p.Key);
+        if (p.Key.Equals(selectedProblemItem)) problemComboBox.SelectedItem = p.Key;
       }
       SetEnabledStateOfControls();
@@ -276,6 +282,6 @@
     }
 
-    private Dictionary<string, Dictionary<ProblemDescription, Tuple<double, List<IRun>>>> GroupRuns() {
-      var groupedRuns = new Dictionary<string, Dictionary<ProblemDescription, Tuple<double, List<IRun>>>>();
+    private Dictionary<string, Dictionary<ProblemInstance, List<IRun>>> GroupRuns() {
+      var groupedRuns = new Dictionary<string, Dictionary<ProblemInstance, List<IRun>>>();
 
       var table = (string)dataTableComboBox.SelectedItem;
@@ -285,9 +291,7 @@
       if (string.IsNullOrEmpty(selectedGroup)) return groupedRuns;
 
-      var selectedProblem = (ProblemDescription)problemComboBox.SelectedItem;
+      var selectedProblem = (ProblemInstance)problemComboBox.SelectedItem;
       if (selectedProblem == null) return groupedRuns;
-
-      var targetsPerProblem = CalculateBestTargetPerProblemInstance(table);
-
+      
       foreach (var x in (from r in Content
                          where (selectedGroup == AllRuns || r.Parameters.ContainsKey(selectedGroup))
@@ -295,14 +299,14 @@
                            && r.Results.ContainsKey(table)
                            && r.Visible
-                         group r by selectedGroup == AllRuns ? AllRuns : r.Parameters[selectedGroup].ToString() into g
+                         let key = selectedGroup == AllRuns ? AllRuns : r.Parameters[selectedGroup].ToString()
+                         group r by key into g
                          select Tuple.Create(g.Key, g.ToList()))) {
-        var pDict = new Dictionary<ProblemDescription, Tuple<double, List<IRun>>>();
-        foreach (var y in (from r in x.Item2
-                           let pd = new ProblemDescription(r)
-                           group r by pd into g
-                           select Tuple.Create(g.Key, g.ToList()))) {
-          pDict[y.Item1] = Tuple.Create(targetsPerProblem[y.Item1], y.Item2);
-        }
-        groupedRuns[x.Item1] = pDict;
+        var pDict = problems.ToDictionary(r => r, _ => new List<IRun>());
+        foreach (var y in x.Item2) {
+          var pd = new ProblemInstance(y);
+          List<IRun> l;
+          if (pDict.TryGetValue(pd, out l)) l.Add(y);
+        }
+        groupedRuns[x.Item1] = pDict.Where(a => a.Value.Count > 0).ToDictionary(a => a.Key, a => a.Value);
       }
 
@@ -326,38 +330,209 @@
 
       var xAxisTitles = new HashSet<string>();
+
+      // hits describes the number of target hits at a certain time for a certain group
+      var hits = new Dictionary<string, SortedList<double, int>>();
+      // misses describes the number of target misses after a certain time for a certain group
+      // for instance when a run ends, but has not achieved all targets, misses describes
+      // how many targets have been left open at the point when the run ended
+      var misses = new Dictionary<string, SortedList<double, int>>();
+
+      var aggregate = aggregateTargetsCheckBox.Checked;
+      double minEff = double.MaxValue, maxEff = double.MinValue;
+      var noRuns = 0;
+      foreach (var group in groupedRuns) {
+        SortedList<double, int> epdfHits = null, epdfMisses = null;
+        if (aggregate) {
+          hits[group.Key] = epdfHits = new SortedList<double, int>();
+          misses[group.Key] = epdfMisses = new SortedList<double, int>();
+        }
+        foreach (var problem in group.Value) {
+          var max = problem.Key.IsMaximization();
+          var absTargets = GetAbsoluteTargets(problem.Key).ToArray();
+          foreach (var run in problem.Value) {
+            noRuns++;
+            var resultsTable = (IndexedDataTable<double>)run.Results[table];
+            xAxisTitles.Add(resultsTable.VisualProperties.XAxisTitle);
+
+            var efforts = absTargets.Select(t => GetEffortToHitTarget(resultsTable.Rows.First().Values, t, max)).ToArray();
+            minEff = Math.Min(minEff, efforts.Min(x => x.Item2));
+            maxEff = Math.Max(maxEff, efforts.Max(x => x.Item2));
+            for (var idx = 0; idx < efforts.Length; idx++) {
+              var e = efforts[idx];
+              if (!aggregate) {
+                var key = group.Key + "@" + (targetsAreRelative
+                  ? (targets[idx] * 100).ToString(CultureInfo.CurrentCulture.NumberFormat) + "%"
+                  : targets[idx].ToString(CultureInfo.CurrentCulture.NumberFormat));
+                if (!hits.TryGetValue(key, out epdfHits))
+                  hits[key] = epdfHits = new SortedList<double, int>();
+                if (!misses.TryGetValue(key, out epdfMisses))
+                  misses[key] = epdfMisses = new SortedList<double, int>();
+              }
+              var list = e.Item1 ? epdfHits : epdfMisses;
+              int v;
+              if (list.TryGetValue(e.Item2, out v))
+                list[e.Item2] = v + 1;
+              else list[e.Item2] = 1;
+            }
+          }
+        }
+      }
+
+      UpdateTargetChartAxisXBounds(minEff, maxEff);
+
+      DrawTargetsEcdf(hits, misses, noRuns);
+
+      if (targets.Length == 1) {
+        if (targetsAreRelative)
+          targetChart.ChartAreas[0].AxisY.Title = "Probability to be " + (targets[0] * 100) + "% worse than best";
+        else targetChart.ChartAreas[0].AxisY.Title = "Probability to reach at least a fitness of " + targets[0];
+      } else targetChart.ChartAreas[0].AxisY.Title = "Proportion of reached targets";
+      targetChart.ChartAreas[0].AxisX.Title = string.Join(" / ", xAxisTitles);
+      targetChart.ChartAreas[0].AxisX.IsLogarithmic = CanDisplayLogarithmic();
+      targetChart.ChartAreas[0].CursorY.Interval = 0.05;
+
+      UpdateErtTables(groupedRuns);
+    }
+
+    private void DrawTargetsEcdf(Dictionary<string, SortedList<double, int>> hits, Dictionary<string, SortedList<double, int>> misses, int noRuns) {
       var colorCount = 0;
       var lineStyleCount = 0;
-
-      // if the group contains multiple different problem instances we want to use the
-      // minimal maximal observed effort otherwise we run into situations where we don't
-      // have data for a certain problem instance anymore this is a special case when
-      // aggregating over multiple problem instances      
-      var maxEfforts = new Dictionary<ProblemDescription, double>();
-      double minEff = double.MaxValue, maxEff = double.MinValue;
-      foreach (var group in groupedRuns) {
-        foreach (var problem in group.Value) {
-          double problemSpecificMaxEff;
-          if (!maxEfforts.TryGetValue(problem.Key, out problemSpecificMaxEff)) {
-            problemSpecificMaxEff = 0;
-          }
-          var bestKnownTarget = problem.Value.Item1;
-          var max = problem.Key.IsMaximization();
-          var worstTarget = (max ? (1 - targets.Max()) : (1 + targets.Max())) * bestKnownTarget;
-          var bestTarget = (max ? (1 - targets.Min()) : (1 + targets.Min())) * bestKnownTarget;
-          foreach (var run in problem.Value.Item2) {
-            var row = ((IndexedDataTable<double>)run.Results[table]).Rows.First().Values;
-            var a = row.FirstOrDefault(x => max ? x.Item2 >= worstTarget : x.Item2 <= worstTarget);
-            var b = row.FirstOrDefault(x => max ? x.Item2 >= bestTarget : x.Item2 <= bestTarget);
-            var firstEff = (a == default(Tuple<double, double>)) ? row.Last().Item1 : a.Item1;
-            var lastEff = (b == default(Tuple<double, double>)) ? row.Last().Item1 : b.Item1;
-            if (minEff > firstEff) minEff = firstEff;
-            if (maxEff < lastEff) maxEff = lastEff;
-            if (problemSpecificMaxEff < lastEff) problemSpecificMaxEff = lastEff;
-          }
-          maxEfforts[problem.Key] = problemSpecificMaxEff;
-        }
-      }
-      maxEff = Math.Min(maxEff, maxEfforts.Values.Min());
-
+      
+      var showMarkers = markerCheckBox.Checked;
+      foreach (var list in hits) {
+        var row = new Series(list.Key) {
+          ChartType = SeriesChartType.StepLine,
+          BorderWidth = 3,
+          Color = colors[colorCount],
+          BorderDashStyle = lineStyles[lineStyleCount],
+        };
+        var rowShade = new Series(list.Key + "-range") {
+          IsVisibleInLegend = false,
+          ChartType = SeriesChartType.Range,
+          Color = Color.FromArgb(32, colors[colorCount]),
+          YValuesPerPoint = 2
+        };
+
+        var ecdf = 0.0;
+        var missedecdf = 0.0;
+        var iter = misses[list.Key].GetEnumerator();
+        var moreMisses = iter.MoveNext();
+        var totalTargets = noRuns;
+        if (aggregateTargetsCheckBox.Checked) totalTargets *= targets.Length;
+        var movingTargets = totalTargets;
+        var labelPrinted = false;
+        foreach (var h in list.Value) {
+          var prevmissedecdf = missedecdf;
+          while (moreMisses && iter.Current.Key <= h.Key) {
+            if (!labelPrinted && row.Points.Count > 0) {
+              var point = row.Points.Last();
+              point.Label = row.Name;
+              point.MarkerStyle = MarkerStyle.Cross;
+              point.MarkerBorderWidth = 1;
+              point.MarkerSize = 10;
+              labelPrinted = true;
+              rowShade.Points.Add(new DataPoint(point.XValue, new[] {ecdf / totalTargets, (ecdf + missedecdf) / totalTargets}));
+            }
+            missedecdf += iter.Current.Value;
+            movingTargets -= iter.Current.Value;
+            if (row.Points.Count > 0 && row.Points.Last().XValue == iter.Current.Key) {
+              row.Points.Last().SetValueY(ecdf / movingTargets);
+              row.Points.Last().Color = Color.FromArgb(255 - (int)Math.Floor(255 * (prevmissedecdf / totalTargets)), colors[colorCount]);
+            } else {
+              var dp = new DataPoint(iter.Current.Key, ecdf / movingTargets) {
+                Color = Color.FromArgb(255 - (int)Math.Floor(255 * (prevmissedecdf / totalTargets)), colors[colorCount])
+              };
+              if (showMarkers) {
+                dp.MarkerStyle = MarkerStyle.Circle;
+                dp.MarkerBorderWidth = 1;
+                dp.MarkerSize = 5;
+              }
+              row.Points.Add(dp);
+              prevmissedecdf = missedecdf;
+            }
+            if (boundShadingCheckBox.Checked) {
+              if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == iter.Current.Key)
+                rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
+              else rowShade.Points.Add(new DataPoint(iter.Current.Key, new[] {ecdf / totalTargets, (ecdf + missedecdf) / totalTargets}));
+            }
+            moreMisses = iter.MoveNext();
+            if (!labelPrinted) {
+              var point = row.Points.Last();
+              point.Label = row.Name;
+              point.MarkerStyle = MarkerStyle.Cross;
+              point.MarkerBorderWidth = 1;
+              point.MarkerSize = 10;
+              labelPrinted = true;
+            }
+          }
+          ecdf += h.Value;
+          if (row.Points.Count > 0 && row.Points.Last().XValue == h.Key) {
+            row.Points.Last().SetValueY(ecdf / movingTargets);
+            row.Points.Last().Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount]);
+          } else {
+            var dp = new DataPoint(h.Key, ecdf / movingTargets) {
+              Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount])
+            };
+            if (showMarkers) {
+              dp.MarkerStyle = MarkerStyle.Circle;
+              dp.MarkerBorderWidth = 1;
+              dp.MarkerSize = 5;
+            }
+            row.Points.Add(dp);
+          }
+          if (missedecdf > 0 && boundShadingCheckBox.Checked) {
+            if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == h.Key)
+              rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
+            else rowShade.Points.Add(new DataPoint(h.Key, new[] {ecdf / totalTargets, (ecdf + missedecdf) / totalTargets}));
+          }
+        }
+
+        while (moreMisses) {
+          // if there are misses beyond the last hit we extend the shaded area
+          missedecdf += iter.Current.Value;
+          var dp = new DataPoint(iter.Current.Key, ecdf / movingTargets) {
+            Color = Color.FromArgb(255 - (int)Math.Floor(255 * (missedecdf / totalTargets)), colors[colorCount])
+          };
+          if (showMarkers) {
+            dp.MarkerStyle = MarkerStyle.Circle;
+            dp.MarkerBorderWidth = 1;
+            dp.MarkerSize = 5;
+          }
+          row.Points.Add(dp);
+          if (boundShadingCheckBox.Checked) {
+            rowShade.Points.Add(new DataPoint(iter.Current.Key, new[] {ecdf / totalTargets, (ecdf + missedecdf) / totalTargets}));
+          }
+          moreMisses = iter.MoveNext();
+
+          if (!labelPrinted && row.Points.Count > 0) {
+            var point = row.Points.Last();
+            point.Label = row.Name;
+            point.MarkerStyle = MarkerStyle.Cross;
+            point.MarkerBorderWidth = 1;
+            point.MarkerSize = 10;
+            labelPrinted = true;
+          }
+        }
+
+        if (!labelPrinted) {
+          var point = row.Points.Last();
+          point.Label = row.Name;
+          point.MarkerStyle = MarkerStyle.Cross;
+          point.MarkerBorderWidth = 1;
+          point.MarkerSize = 10;
+          rowShade.Points.Add(new DataPoint(point.XValue, new[] {ecdf / totalTargets, (ecdf + missedecdf) / totalTargets}));
+          labelPrinted = true;
+        }
+
+        ConfigureSeries(row);
+        targetChart.Series.Add(rowShade);
+        targetChart.Series.Add(row);
+
+        colorCount = (colorCount + 1) % colors.Length;
+        if (colorCount == 0) lineStyleCount = (lineStyleCount + 1) % lineStyles.Length;
+      }
+    }
+
+    private void UpdateTargetChartAxisXBounds(double minEff, double maxEff) {
       var minZeros = (int)Math.Floor(Math.Log10(minEff));
       var maxZeros = (int)Math.Floor(Math.Log10(maxEff));
@@ -372,118 +547,32 @@
       targetChart.ChartAreas[0].AxisX.Minimum = (double)axisMin;
       targetChart.ChartAreas[0].AxisX.Maximum = (double)axisMax;
-
-      foreach (var group in groupedRuns) {
-        // hits describes the number of target hits at a certain time for a certain group
-        var hits = new Dictionary<string, SortedList<double, int>>();
-        // misses describes the number of target misses after a certain time for a certain group
-        // for instance when a run ends, but has not achieved all targets, misses describes
-        // how many targets have been left open at the point when the run ended
-        var misses = new Dictionary<string, SortedList<double, int>>();
-        var maxLength = 0.0;
-
-        var noRuns = 0;
-        foreach (var problem in group.Value) {
-          foreach (var run in problem.Value.Item2) {
-            var resultsTable = (IndexedDataTable<double>)run.Results[table];
-            xAxisTitles.Add(resultsTable.VisualProperties.XAxisTitle);
-
-            if (aggregateTargetsCheckBox.Checked) {
-              var length = CalculateHitsForAllTargets(hits, misses, resultsTable.Rows.First(), problem.Key, group.Key, problem.Value.Item1, maxEff);
-              maxLength = Math.Max(length, maxLength);
-            } else {
-              CalculateHitsForEachTarget(hits, misses, resultsTable.Rows.First(), problem.Key, group.Key, problem.Value.Item1, maxEff);
-            }
-            noRuns++;
-          }
-        }
-        foreach (var list in hits) {
-          var row = new Series(list.Key) {
-            ChartType = SeriesChartType.StepLine,
-            BorderWidth = 2,
-            Color = colors[colorCount],
-            BorderDashStyle = lineStyles[lineStyleCount],
-          };
-          var rowShade = new Series(list.Key + "-range") {
-            IsVisibleInLegend = false,
-            ChartType = SeriesChartType.Range,
-            Color = Color.FromArgb(32, colors[colorCount])
-          };
-
-          var ecdf = 0.0;
-          var missedecdf = 0.0;
-          var iter = misses[list.Key].GetEnumerator();
-          var moreMisses = iter.MoveNext();
-          var totalTargets = noRuns;
-          if (aggregateTargetsCheckBox.Checked) totalTargets *= targets.Length;
-          var movingTargets = totalTargets;
-          foreach (var h in list.Value) {
-            while (moreMisses && iter.Current.Key <= h.Key) {
-              missedecdf += iter.Current.Value;
-              movingTargets -= iter.Current.Value;
-              if (row.Points.Count > 0 && row.Points.Last().XValue == iter.Current.Key)
-                row.Points.Last().SetValueY(ecdf / movingTargets);
-              else row.Points.AddXY(iter.Current.Key, ecdf / movingTargets);
-              if (boundShadingCheckBox.Checked) {
-                if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == iter.Current.Key)
-                  rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
-                else rowShade.Points.Add(new DataPoint(iter.Current.Key, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
-              }
-              moreMisses = iter.MoveNext();
-            }
-            ecdf += h.Value;
-            if (row.Points.Count > 0 && row.Points.Last().XValue == h.Key)
-              row.Points.Last().SetValueY(ecdf / movingTargets);
-            else row.Points.AddXY(h.Key, ecdf / movingTargets);
-            if (missedecdf > 0 && boundShadingCheckBox.Checked) {
-              if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == h.Key)
-                rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
-              else rowShade.Points.Add(new DataPoint(h.Key, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
-            }
-          }
-
-          while (moreMisses) {
-            // if there are misses beyond the last hit we extend the shaded area
-            missedecdf += iter.Current.Value;
-            //movingTargets -= iter.Current.Value;
-            if (row.Points.Count > 0 && row.Points.Last().XValue == iter.Current.Key)
-              row.Points.Last().SetValueY(ecdf / movingTargets);
-            else row.Points.AddXY(iter.Current.Key, ecdf / movingTargets);
-            if (boundShadingCheckBox.Checked) {
-              if (rowShade.Points.Count > 0 && rowShade.Points.Last().XValue == iter.Current.Key)
-                rowShade.Points.Last().SetValueY(ecdf / totalTargets, (ecdf + missedecdf) / totalTargets);
-              else rowShade.Points.Add(new DataPoint(iter.Current.Key, new[] { ecdf / totalTargets, (ecdf + missedecdf) / totalTargets }));
-            }
-            moreMisses = iter.MoveNext();
-          }
-
-          if (maxLength > 0 && (row.Points.Count == 0 || row.Points.Last().XValue < maxLength))
-            row.Points.AddXY(maxLength, ecdf / movingTargets);
-
-          if (row.Points.Count > 0) {
-            var point = row.Points.Last();
-            point.Label = row.Name;
-            point.MarkerStyle = MarkerStyle.Cross;
-            point.MarkerBorderWidth = 1;
-          }
-
-          ConfigureSeries(row);
-          targetChart.Series.Add(rowShade);
-          targetChart.Series.Add(row);
-        }
-        colorCount = (colorCount + 1) % colors.Length;
-        if (colorCount == 0) lineStyleCount = (lineStyleCount + 1) % lineStyles.Length;
-      }
-
-      if (targets.Length == 1)
-        targetChart.ChartAreas[0].AxisY.Title = "Probability to be " + (targets[0] * 100) + "% worse than best";
-      else targetChart.ChartAreas[0].AxisY.Title = "Proportion of reached targets";
-      targetChart.ChartAreas[0].AxisX.Title = string.Join(" / ", xAxisTitles);
-      targetChart.ChartAreas[0].AxisX.IsLogarithmic = CanDisplayLogarithmic();
-      targetChart.ChartAreas[0].CursorY.Interval = 0.05;
-      UpdateErtTables(groupedRuns);
+    }
+
+    private IEnumerable<double> GetAbsoluteTargets(ProblemInstance pInstance) {
+      if (!targetsAreRelative) return targets;
+      var maximization = pInstance.IsMaximization();
+      var bestKnown = pInstance.BestKnownQuality;
+      if (double.IsNaN(bestKnown)) throw new ArgumentException("Problem instance does not have a defined best - known quality.");
+      IEnumerable<double> tmp = null;
+      if (bestKnown > 0) {
+        tmp = targets.Select(x => (maximization ? (1 - x) : (1 + x)) * bestKnown);
+      } else if (bestKnown < 0) {
+        tmp = targets.Select(x => (!maximization ? (1 - x) : (1 + x)) * bestKnown);
+      } else {
+        // relative to 0 is impossible
+        tmp = targets;
+      }
+      return tmp;
+    }
+
+    private double[] GetAbsoluteTargetsWorstToBest(ProblemInstance pInstance) {
+      if (double.IsNaN(pInstance.BestKnownQuality)) throw new ArgumentException("Problem instance does not have a defined best-known quality.");
+      var absTargets = GetAbsoluteTargets(pInstance);
+      return (pInstance.IsMaximization()
+        ? absTargets.OrderBy(x => x) : absTargets.OrderByDescending(x => x)).ToArray();
     }
 
     private void GenerateDefaultTargets() {
-      targets = new[] { 0.1, 0.05, 0.02, 0.01, 0 };
+      targets = new[] { 0.1, 0.095, 0.09, 0.085, 0.08, 0.075, 0.07, 0.065, 0.06, 0.055, 0.05, 0.045, 0.04, 0.035, 0.03, 0.025, 0.02, 0.015, 0.01, 0.005, 0 };
       suppressTargetsEvents = true;
       targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%";
@@ -491,71 +580,19 @@
     }
 
-    private void CalculateHitsForEachTarget(Dictionary<string, SortedList<double, int>> hits,
-                                            Dictionary<string, SortedList<double, int>> misses,
-                                            IndexedDataRow<double> row, ProblemDescription problem,
-                                            string group, double bestTarget, double maxEffort) {
-      foreach (var t in targets.Select(x => Tuple.Create((problem.IsMaximization() ? (1 - x) : (1 + x)) * bestTarget, x))) {
-        var l = t.Item1;
-        var key = group + "_" + (t.Item2 * 100) + "%_" + l;
-        if (!hits.ContainsKey(key)) {
-          hits.Add(key, new SortedList<double, int>());
-          misses.Add(key, new SortedList<double, int>());
-        }
-        var hit = false;
-        foreach (var v in row.Values) {
-          if (v.Item1 > maxEffort) break;
-          if (problem.IsMaximization() && v.Item2 >= l || !problem.IsMaximization() && v.Item2 <= l) {
-            if (hits[key].ContainsKey(v.Item1))
-              hits[key][v.Item1]++;
-            else hits[key][v.Item1] = 1;
-            hit = true;
-            break;
-          }
-        }
-        if (!hit) {
-          var max = Math.Min(row.Values.Last().Item1, maxEffort);
-          if (misses[key].ContainsKey(max))
-            misses[key][max]++;
-          else misses[key][max] = 1;
-        }
-      }
-    }
-
-    private double CalculateHitsForAllTargets(Dictionary<string, SortedList<double, int>> hits,
-                                              Dictionary<string, SortedList<double, int>> misses,
-                                              IndexedDataRow<double> row, ProblemDescription problem,
-                                              string group, double bestTarget, double maxEffort) {
-      var values = row.Values;
-      if (!hits.ContainsKey(group)) {
-        hits.Add(group, new SortedList<double, int>());
-        misses.Add(group, new SortedList<double, int>());
-      }
-
-      var i = 0;
-      var j = 0;
-      while (i < targets.Length && j < values.Count) {
-        var target = (problem.IsMaximization() ? (1 - targets[i]) : (1 + targets[i])) * bestTarget;
-        var current = values[j];
-        if (current.Item1 > maxEffort) break;
-        if (problem.IsMaximization() && current.Item2 >= target
-          || !problem.IsMaximization() && current.Item2 <= target) {
-          if (hits[group].ContainsKey(current.Item1)) hits[group][current.Item1]++;
-          else hits[group][current.Item1] = 1;
-          i++;
-        } else {
-          j++;
-        }
-      }
-      if (j == values.Count) j--;
-      var effort = Math.Min(values[j].Item1, maxEffort);
-      if (i < targets.Length) {
-        if (misses[group].ContainsKey(effort))
-          misses[group][effort] += targets.Length - i;
-        else misses[group][effort] = targets.Length - i;
-      }
-      return effort;
-    }
-
-    private void UpdateErtTables(Dictionary<string, Dictionary<ProblemDescription, Tuple<double, List<IRun>>>> groupedRuns) {
+    private Tuple<bool, double> GetEffortToHitTarget(
+        IEnumerable<Tuple<double, double>> convergenceGraph,
+        double absTarget, bool maximization) {
+      var hit = false;
+      var effort = double.NaN;
+      foreach (var dent in convergenceGraph) {
+        effort = dent.Item1;
+        hit = maximization && dent.Item2 >= absTarget || !maximization && dent.Item2 <= absTarget;
+        if (hit) break;
+      }
+      if (double.IsNaN(effort)) throw new ArgumentException("Convergence graph is empty.", "convergenceGraph");
+      return Tuple.Create(hit, effort);
+    }
+
+    private void UpdateErtTables(Dictionary<string, Dictionary<ProblemInstance, List<IRun>>> groupedRuns) {
       ertTableView.Content = null;
       var columns = 1 + targets.Length + 1;
@@ -565,18 +602,13 @@
       var tableName = (string)dataTableComboBox.SelectedItem;
       if (string.IsNullOrEmpty(tableName)) return;
-
-      var targetsPerProblem = CalculateBestTargetPerProblemInstance(tableName);
-
-      var colNames = new string[columns];
-      colNames[0] = colNames[colNames.Length - 1] = string.Empty;
-      for (var i = 0; i < targets.Length; i++) {
-        colNames[i + 1] = targets[i].ToString("0.0%");
-      }
+      
       var problems = groupedRuns.SelectMany(x => x.Value.Keys).Distinct().ToList();
 
       foreach (var problem in problems) {
+        var max = problem.IsMaximization();
         matrix[rowCount, 0] = problem.ToString();
-        for (var i = 0; i < targets.Length; i++) {
-          matrix[rowCount, i + 1] = (targetsPerProblem[problem] * (problem.IsMaximization() ? (1 - targets[i]) : (1 + targets[i]))).ToString(CultureInfo.CurrentCulture.NumberFormat);
+        var absTargets = GetAbsoluteTargetsWorstToBest(problem);
+        for (var i = 0; i < absTargets.Length; i++) {
+          matrix[rowCount, i + 1] = absTargets[i].ToString(CultureInfo.CurrentCulture.NumberFormat);
         }
         matrix[rowCount, columns - 1] = "#succ";
@@ -590,8 +622,8 @@
             continue;
           }
-          var runs = group.Value[problem].Item2;
-          ErtCalculationResult result = default(ErtCalculationResult);
-          for (var i = 0; i < targets.Length; i++) {
-            result = ExpectedRuntimeHelper.CalculateErt(runs, tableName, (problem.IsMaximization() ? (1 - targets[i]) : (1 + targets[i])) * group.Value[problem].Item1, problem.IsMaximization());
+          var runs = group.Value[problem];
+          var result = default(ErtCalculationResult);
+          for (var i = 0; i < absTargets.Length; i++) {
+            result = ExpectedRuntimeHelper.CalculateErt(runs, tableName, absTargets[i], max);
             matrix[rowCount, i + 1] = result.ToString();
           }
@@ -600,5 +632,5 @@
         }
       }
-      ertTableView.Content = new StringMatrix(matrix) { ColumnNames = colNames };
+      ertTableView.Content = new StringMatrix(matrix);
       ertTableView.DataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
     }
@@ -621,18 +653,16 @@
       var colorCount = 0;
       var lineStyleCount = 0;
-
-      var targetsPerProblem = CalculateBestTargetPerProblemInstance((string)dataTableComboBox.SelectedItem);
-
+      
       foreach (var group in groupedRuns) {
         var hits = new Dictionary<string, SortedList<double, double>>();
 
         foreach (var problem in group.Value) {
-          foreach (var run in problem.Value.Item2) {
+          foreach (var run in problem.Value) {
             var resultsTable = (IndexedDataTable<double>)run.Results[table];
 
-            if (eachOrAllBudgetsCheckBox.Checked) {
-              CalculateHitsForEachBudget(hits, resultsTable.Rows.First(), group.Value.Count, problem.Key, group.Key, problem.Value.Item2.Count, targetsPerProblem[problem.Key]);
+            if (aggregateBudgetsCheckBox.Checked) {
+              CalculateHitsForAllBudgets(hits, resultsTable.Rows.First(), group.Value.Count, problem.Key, group.Key, problem.Value.Count);
             } else {
-              CalculateHitsForAllBudgets(hits, resultsTable.Rows.First(), group.Value.Count, problem.Key, group.Key, problem.Value.Item2.Count, targetsPerProblem[problem.Key]);
+              CalculateHitsForEachBudget(hits, resultsTable.Rows.First(), group.Value.Count, problem.Key, group.Key, problem.Value.Count);
             }
           }
@@ -667,19 +697,9 @@
 
     private void GenerateDefaultBudgets(string table) {
-      var runs = GroupRuns().SelectMany(x => x.Value.Values).SelectMany(x => x.Item2).ToList();
+      var runs = GroupRuns().SelectMany(x => x.Value.Values).SelectMany(x => x).ToList();
       var min = runs.Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Select(y => y.Item1).Min()).Min();
       var max = runs.Select(x => ((IndexedDataTable<double>)x.Results[table]).Rows.First().Values.Select(y => y.Item1).Max()).Max();
-
-      var maxMagnitude = (int)Math.Ceiling(Math.Log10(max));
-      var minMagnitude = (int)Math.Floor(Math.Log10(min));
-      if (maxMagnitude - minMagnitude >= 3) {
-        budgets = new double[maxMagnitude - minMagnitude];
-        for (var i = minMagnitude; i < maxMagnitude; i++) {
-          budgets[i - minMagnitude] = Math.Pow(10, i);
-        }
-      } else {
-        var range = max - min;
-        budgets = Enumerable.Range(0, 6).Select(x => min + (x / 5.0) * range).ToArray();
-      }
+      var points = 20;
+      budgets = Enumerable.Range(1, points).Select(x => min + (x / (double)points) * (max - min)).ToArray();
       suppressBudgetsEvents = true;
       budgetsTextBox.Text = string.Join(" ; ", budgets);
@@ -687,5 +707,6 @@
     }
 
-    private void CalculateHitsForEachBudget(Dictionary<string, SortedList<double, double>> hits, IndexedDataRow<double> row, int groupCount, ProblemDescription problem, string groupName, int problemCount, double bestTarget) {
+    private void CalculateHitsForEachBudget(Dictionary<string, SortedList<double, double>> hits, IndexedDataRow<double> row, int groupCount, ProblemInstance problem, string groupName, int problemCount) {
+      var max = problem.IsMaximization();
       foreach (var b in budgets) {
         var key = groupName + "-" + b;
@@ -697,8 +718,8 @@
             if (prev == null && v.Item1 != b) break;
             var tgt = ((prev == null || v.Item1 == b) ? v.Item2 : prev.Item2);
-            tgt = problem.IsMaximization() ? bestTarget / tgt : tgt / bestTarget;
-            if (hits[key].ContainsKey(tgt))
-              hits[key][tgt] += 1.0 / (groupCount * problemCount);
-            else hits[key][tgt] = 1.0 / (groupCount * problemCount);
+            var relTgt = CalculateRelativeDifference(max, problem.BestKnownQuality, tgt) + 1;
+            if (hits[key].ContainsKey(relTgt))
+              hits[key][relTgt] += 1.0 / (groupCount * problemCount);
+            else hits[key][relTgt] = 1.0 / (groupCount * problemCount);
             break;
           }
@@ -709,5 +730,5 @@
     }
 
-    private void CalculateHitsForAllBudgets(Dictionary<string, SortedList<double, double>> hits, IndexedDataRow<double> row, int groupCount, ProblemDescription problem, string groupName, int problemCount, double bestTarget) {
+    private void CalculateHitsForAllBudgets(Dictionary<string, SortedList<double, double>> hits, IndexedDataRow<double> row, int groupCount, ProblemInstance problem, string groupName, int problemCount) {
       var values = row.Values;
       if (!hits.ContainsKey(groupName)) hits.Add(groupName, new SortedList<double, double>());
@@ -716,4 +737,5 @@
       var j = 0;
       Tuple<double, double> prev = null;
+      var max = problem.IsMaximization();
       while (i < budgets.Length && j < values.Count) {
         var current = values[j];
@@ -721,7 +743,7 @@
           if (prev != null || current.Item1 == budgets[i]) {
             var tgt = (prev == null || current.Item1 == budgets[i]) ? current.Item2 : prev.Item2;
-            tgt = problem.IsMaximization() ? bestTarget / tgt : tgt / bestTarget;
-            if (!hits[groupName].ContainsKey(tgt)) hits[groupName][tgt] = 0;
-            hits[groupName][tgt] += 1.0 / (groupCount * problemCount * budgets.Length);
+            var relTgt = CalculateRelativeDifference(max, problem.BestKnownQuality, tgt) + 1;
+            if (!hits[groupName].ContainsKey(relTgt)) hits[groupName][relTgt] = 0;
+            hits[groupName][relTgt] += 1.0 / (groupCount * problemCount * budgets.Length);
           }
           i++;
@@ -732,8 +754,8 @@
       }
       var lastTgt = values.Last().Item2;
-      lastTgt = problem.IsMaximization() ? bestTarget / lastTgt : lastTgt / bestTarget;
-      if (i < budgets.Length && !hits[groupName].ContainsKey(lastTgt)) hits[groupName][lastTgt] = 0;
+      var lastRelTgt = CalculateRelativeDifference(max, problem.BestKnownQuality, lastTgt) + 1;
+      if (i < budgets.Length && !hits[groupName].ContainsKey(lastRelTgt)) hits[groupName][lastRelTgt] = 0;
       while (i < budgets.Length) {
-        hits[groupName][lastTgt] += 1.0 / (groupCount * problemCount * budgets.Length);
+        hits[groupName][lastRelTgt] += 1.0 / (groupCount * problemCount * budgets.Length);
         i++;
       }
@@ -756,4 +778,5 @@
       if (dataTableComboBox.SelectedIndex >= 0)
         GenerateDefaultBudgets((string)dataTableComboBox.SelectedItem);
+      UpdateBestKnownQualities();
       UpdateRuns();
       SetEnabledStateOfControls();
@@ -782,5 +805,7 @@
           return;
         }
-        targetList.Add(t / 100);
+        if (targetsAreRelative)
+          targetList.Add(t / 100);
+        else targetList.Add(t);
       }
       if (targetList.Count == 0) {
@@ -791,7 +816,11 @@
       e.Cancel = false;
       errorProvider.SetError(targetsTextBox, null);
-      targets = targetList.Select(x => (double)x).OrderByDescending(x => x).ToArray();
+      targets = targetsAreRelative ? targetList.Select(x => (double)x).OrderByDescending(x => x).ToArray() : targetList.Select(x => (double)x).ToArray();
       suppressTargetsEvents = true;
-      try { targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%"; } finally { suppressTargetsEvents = false; }
+      try {
+        if (targetsAreRelative)
+          targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%";
+        else targetsTextBox.Text = string.Join(" ; ", targets);
+      } finally { suppressTargetsEvents = false; }
 
       UpdateResultsByTarget();
@@ -804,4 +833,31 @@
         UpdateResultsByTarget();
       } finally { ResumeRepaint(true); }
+    }
+
+    private void relativeOrAbsoluteComboBox_SelectedIndexChanged(object sender, EventArgs e) {
+      if (suppressUpdates) return;
+      var pd = (ProblemInstance)problemComboBox.SelectedItem;
+      if (!double.IsNaN(pd.BestKnownQuality)) {
+        var max = pd.IsMaximization();
+        if (targetsAreRelative) targets = GetAbsoluteTargets(pd).ToArray();
+        else {
+          // Rounding to 5 digits since it's certainly appropriate for this application
+          if (pd.BestKnownQuality > 0) {
+            targets = targets.Select(x => Math.Round(max ? 1.0 - (x / pd.BestKnownQuality) : (x / pd.BestKnownQuality) - 1.0, 5)).ToArray();
+          } else if (pd.BestKnownQuality < 0) {
+            targets = targets.Select(x => Math.Round(!max ? 1.0 - (x / pd.BestKnownQuality) : (x / pd.BestKnownQuality) - 1.0, 5)).ToArray();
+          }
+        }
+      }
+      targetsAreRelative = (string)relativeOrAbsoluteComboBox.SelectedItem == "relative";
+      SuspendRepaint();
+      suppressTargetsEvents = true;
+      try {
+        if (targetsAreRelative)
+          targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%";
+        else targetsTextBox.Text = string.Join(" ; ", targets);
+
+        UpdateResultsByTarget();
+      } finally { suppressTargetsEvents = false; ResumeRepaint(true); }
     }
 
@@ -823,5 +879,7 @@
             targets = dialog.Values.Select(x => (double)x).ToArray();
             suppressTargetsEvents = true;
-            targetsTextBox.Text = string.Join("% ; ", targets);
+            if (targetsAreRelative)
+              targetsTextBox.Text = string.Join("% ; ", targets);
+            else targetsTextBox.Text = string.Join(" ; ", targets);
             suppressTargetsEvents = false;
 
@@ -835,7 +893,6 @@
     private void addTargetsAsResultButton_Click(object sender, EventArgs e) {
       var table = (string)dataTableComboBox.SelectedItem;
-
-      var targetsPerProblem = CalculateBestTargetPerProblemInstance(table);
-
+      if (string.IsNullOrEmpty(table)) return;
+      
       foreach (var run in Content) {
         if (!run.Results.ContainsKey(table)) continue;
@@ -844,10 +901,11 @@
         var i = 0;
         var j = 0;
-        var pd = new ProblemDescription(run);
-        while (i < targets.Length && j < values.Count) {
-          var target = (pd.IsMaximization() ? (1 - targets[i]) : (1 + targets[i])) * targetsPerProblem[pd];
+        var pd = new ProblemInstance(run);
+        var max = pd.IsMaximization();
+        var absTargets = GetAbsoluteTargetsWorstToBest(problems.Single(x => x.Equals(pd)));
+        while (i < absTargets.Length && j < values.Count) {
+          var target = absTargets[i];
           var current = values[j];
-          if (pd.IsMaximization() && current.Item2 >= target
-              || !pd.IsMaximization() && current.Item2 <= target) {
+          if (max && current.Item2 >= target || !max && current.Item2 <= target) {
             run.Results[table + ".Target" + target] = new DoubleValue(current.Item1);
             i++;
@@ -857,4 +915,11 @@
         }
       }
+    }
+
+    private void markerCheckBox_CheckedChanged(object sender, EventArgs e) {
+      SuspendRepaint();
+      try {
+        UpdateResultsByTarget();
+      } finally { ResumeRepaint(true); }
     }
     #endregion
@@ -887,7 +952,5 @@
     }
 
-    private void eachOrAllBudgetsCheckBox_CheckedChanged(object sender, EventArgs e) {
-      var each = eachOrAllBudgetsCheckBox.Checked;
-      eachOrAllBudgetsCheckBox.Text = each ? "each" : "all";
+    private void aggregateBudgetsCheckBox_CheckedChanged(object sender, EventArgs e) {
       SuspendRepaint();
       try {
@@ -967,14 +1030,31 @@
 
     #region Helpers
-    private Dictionary<ProblemDescription, double> CalculateBestTargetPerProblemInstance(string table) {
+    private double CalculateRelativeDifference(bool maximization, double bestKnown, double fit) {
+      if (bestKnown == 0) {
+        // no relative difference with respect to bestKnown possible
+        return maximization ? -fit : fit;
+      }
+      var absDiff = (fit - bestKnown);
+      var relDiff = absDiff / bestKnown;
+      if (maximization) {
+        return bestKnown > 0 ? -relDiff : relDiff;
+      } else {
+        return bestKnown > 0 ? relDiff : -relDiff;
+      }
+    }
+
+    private Dictionary<ProblemInstance, double> CalculateBestTargetPerProblemInstance(string table) {
+      if (table == null) table = string.Empty;
       return (from r in Content
               where r.Visible
-              let pd = new ProblemDescription(r)
+              let pd = new ProblemInstance(r)
               let target = r.Parameters.ContainsKey("BestKnownQuality")
                            && r.Parameters["BestKnownQuality"] is DoubleValue
                 ? ((DoubleValue)r.Parameters["BestKnownQuality"]).Value
-                : ((IndexedDataTable<double>)r.Results[table]).Rows.First().Values.Last().Item2
+                : (r.Results.ContainsKey(table) && r.Results[table] is IndexedDataTable<double>
+                  ? ((IndexedDataTable<double>)r.Results[table]).Rows.First().Values.Last().Item2
+                  : double.NaN)
               group target by pd into g
-              select new { Problem = g.Key, Target = g.Key.IsMaximization() ? g.Max() : g.Min() })
+              select new { Problem = g.Key, Target = g.Any(x => !double.IsNaN(x)) ? (g.Key.IsMaximization() ? g.Max() : g.Where(x => !double.IsNaN(x)).Min()) : double.NaN })
         .ToDictionary(x => x.Problem, x => x.Target);
     }
@@ -990,4 +1070,18 @@
         UpdateResultsByCost();
       } finally { ResumeRepaint(true); }
+    }
+
+    private void UpdateBestKnownQualities() {
+      var table = (string)dataTableComboBox.SelectedItem;
+      if (string.IsNullOrEmpty(table)) return;
+
+      var targetsPerProblem = CalculateBestTargetPerProblemInstance(table);
+      foreach (var pd in problems) {
+        double bkq;
+        if (targetsPerProblem.TryGetValue(pd, out bkq))
+          pd.BestKnownQuality = bkq;
+        else pd.BestKnownQuality = double.NaN;
+      }
+      //problemComboBox.ResetBindings();
     }
     #endregion
@@ -1078,11 +1172,11 @@
     }
 
-    private class ProblemDescription {
+    private class ProblemInstance : INotifyPropertyChanged {
       private readonly bool matchAll;
-      public static readonly ProblemDescription MatchAll = new ProblemDescription() {
+      public static readonly ProblemInstance MatchAll = new ProblemInstance() {
         ProblemName = "All with Best-Known"
       };
 
-      private ProblemDescription() {
+      private ProblemInstance() {
         ProblemType = string.Empty;
         ProblemName = string.Empty;
@@ -1094,7 +1188,8 @@
         DisplayMaximization = false;
         matchAll = true;
-      }
-
-      public ProblemDescription(IRun run) {
+        BestKnownQuality = double.NaN;
+      }
+
+      public ProblemInstance(IRun run) {
         ProblemType = GetStringValueOrEmpty(run, "Problem Type");
         ProblemName = GetStringValueOrEmpty(run, "Problem Name");
@@ -1106,14 +1201,88 @@
         DisplayMaximization = !string.IsNullOrEmpty(Maximization);
         matchAll = false;
-      }
-
-      public bool DisplayProblemType { get; set; }
-      public string ProblemType { get; set; }
-      public bool DisplayProblemName { get; set; }
-      public string ProblemName { get; set; }
-      public bool DisplayEvaluator { get; set; }
-      public string Evaluator { get; set; }
-      public bool DisplayMaximization { get; set; }
-      public string Maximization { get; set; }
+        BestKnownQuality = GetDoubleValueOrNaN(run, "BestKnownQuality");
+      }
+
+      private bool displayProblemType;
+      public bool DisplayProblemType {
+        get { return displayProblemType; }
+        set {
+          if (displayProblemType == value) return;
+          displayProblemType = value;
+          OnPropertyChanged("DisplayProblemType");
+        }
+      }
+      private string problemType;
+      public string ProblemType {
+        get { return problemType; }
+        set {
+          if (problemType == value) return;
+          problemType = value;
+          OnPropertyChanged("ProblemType");
+        }
+      }
+      private bool displayProblemName;
+      public bool DisplayProblemName {
+        get { return displayProblemName; }
+        set {
+          if (displayProblemName == value) return;
+          displayProblemName = value;
+          OnPropertyChanged("DisplayProblemName");
+        }
+      }
+      private string problemName;
+      public string ProblemName {
+        get { return problemName; }
+        set {
+          if (problemName == value) return;
+          problemName = value;
+          OnPropertyChanged("ProblemName");
+        }
+      }
+      private bool displayEvaluator;
+      public bool DisplayEvaluator {
+        get { return displayEvaluator; }
+        set {
+          if (displayEvaluator == value) return;
+          displayEvaluator = value;
+          OnPropertyChanged("DisplayEvaluator");
+        }
+      }
+      private string evaluator;
+      public string Evaluator {
+        get { return evaluator; }
+        set {
+          if (evaluator == value) return;
+          evaluator = value;
+          OnPropertyChanged("Evaluator");
+        }
+      }
+      private bool displayMaximization;
+      public bool DisplayMaximization {
+        get { return displayMaximization; }
+        set {
+          if (displayMaximization == value) return;
+          displayMaximization = value;
+          OnPropertyChanged("DisplayMaximization");
+        }
+      }
+      private string maximization;
+      public string Maximization {
+        get { return maximization; }
+        set {
+          if (maximization == value) return;
+          maximization = value;
+          OnPropertyChanged("Maximization");
+        }
+      }
+      private double bestKnownQuality;
+      public double BestKnownQuality {
+        get { return bestKnownQuality; }
+        set {
+          if (bestKnownQuality == value) return;
+          bestKnownQuality = value;
+          OnPropertyChanged("BestKnownQuality");
+        }
+      }
 
       public bool IsMaximization() {
@@ -1129,14 +1298,33 @@
       }
 
+      private double GetDoubleValueOrNaN(IRun run, string key) {
+        IItem param;
+        if (run.Parameters.TryGetValue(key, out param)) {
+          var dv = param as DoubleValue;
+          return dv != null ? dv.Value : double.NaN;
+        }
+        return double.NaN;
+      }
+
       private string GetStringValueOrEmpty(IRun run, string key) {
-        return run.Parameters.ContainsKey(key) ? ((StringValue)run.Parameters[key]).Value : string.Empty;
+        IItem param;
+        if (run.Parameters.TryGetValue(key, out param)) {
+          var sv = param as StringValue;
+          return sv != null ? sv.Value : string.Empty;
+        }
+        return string.Empty;
       }
 
       private string GetMaximizationValueOrEmpty(IRun run, string key) {
-        return run.Parameters.ContainsKey(key) ? (((BoolValue)run.Parameters[key]).Value ? "MAX" : "MIN") : string.Empty;
+        IItem param;
+        if (run.Parameters.TryGetValue(key, out param)) {
+          var bv = param as BoolValue;
+          return bv != null ? (bv.Value ? "MAX" : "MIN") : string.Empty;
+        }
+        return string.Empty;
       }
 
       public override bool Equals(object obj) {
-        var other = obj as ProblemDescription;
+        var other = obj as ProblemInstance;
         if (other == null) return false;
         return ProblemType == other.ProblemType
@@ -1155,5 +1343,12 @@
           (DisplayProblemName ? ProblemName : string.Empty),
           (DisplayEvaluator ? Evaluator : string.Empty),
-          (DisplayMaximization ? Maximization : string.Empty)}.Where(x => !string.IsNullOrEmpty(x)));
+          (DisplayMaximization ? Maximization : string.Empty),
+          !double.IsNaN(BestKnownQuality) ? BestKnownQuality.ToString(CultureInfo.CurrentCulture.NumberFormat) : string.Empty }.Where(x => !string.IsNullOrEmpty(x)));
+      }
+
+      public event PropertyChangedEventHandler PropertyChanged;
+      protected virtual void OnPropertyChanged(string propertyName = null) {
+        var handler = PropertyChanged;
+        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
       }
     }
