Index: /stable/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.Designer.cs
===================================================================
--- /stable/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.Designer.cs	(revision 16835)
+++ /stable/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.Designer.cs	(revision 16836)
@@ -87,5 +87,5 @@
       // dataTableComboBox
       // 
-      this.dataTableComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+      this.dataTableComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
             | System.Windows.Forms.AnchorStyles.Right)));
       this.dataTableComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
@@ -117,5 +117,5 @@
       // groupComboBox
       // 
-      this.groupComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+      this.groupComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
             | System.Windows.Forms.AnchorStyles.Right)));
       this.groupComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
@@ -141,5 +141,5 @@
       // targetsTextBox
       // 
-      this.targetsTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+      this.targetsTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
             | System.Windows.Forms.AnchorStyles.Right)));
       this.targetsTextBox.Location = new System.Drawing.Point(59, 8);
@@ -198,5 +198,5 @@
       // budgetsTextBox
       // 
-      this.budgetsTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+      this.budgetsTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
             | System.Windows.Forms.AnchorStyles.Right)));
       this.budgetsTextBox.Location = new System.Drawing.Point(59, 8);
@@ -233,6 +233,6 @@
       // tabControl
       // 
-      this.tabControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
-            | System.Windows.Forms.AnchorStyles.Left) 
+      this.tabControl.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.tabControl.Controls.Add(this.byTargetTabPage);
@@ -247,4 +247,5 @@
       // byTargetTabPage
       // 
+      this.byTargetTabPage.BackColor = System.Drawing.SystemColors.Window;
       this.byTargetTabPage.Controls.Add(this.relativeOrAbsoluteComboBox);
       this.byTargetTabPage.Controls.Add(this.targetChart);
@@ -264,5 +265,5 @@
       this.byTargetTabPage.TabIndex = 0;
       this.byTargetTabPage.Text = "Performance by Target";
-      this.byTargetTabPage.UseVisualStyleBackColor = true;
+      this.byTargetTabPage.UseVisualStyleBackColor = false;
       // 
       // relativeOrAbsoluteComboBox
@@ -282,6 +283,6 @@
       // targetChart
       // 
-      this.targetChart.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
-            | System.Windows.Forms.AnchorStyles.Left) 
+      this.targetChart.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)));
       chartArea2.AxisX.IsStartedFromZero = false;
@@ -352,4 +353,5 @@
       // byCostTabPage
       // 
+      this.byCostTabPage.BackColor = System.Drawing.SystemColors.Window;
       this.byCostTabPage.Controls.Add(this.byCostViewHost);
       this.byCostTabPage.Controls.Add(this.budgetLogScalingCheckBox);
@@ -364,10 +366,10 @@
       this.byCostTabPage.TabIndex = 1;
       this.byCostTabPage.Text = "Performance by Cost";
-      this.byCostTabPage.UseVisualStyleBackColor = true;
+      this.byCostTabPage.UseVisualStyleBackColor = false;
       // 
       // byCostViewHost
       // 
-      this.byCostViewHost.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
-            | System.Windows.Forms.AnchorStyles.Left) 
+      this.byCostViewHost.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.byCostViewHost.Caption = "View";
@@ -407,4 +409,5 @@
       // byTableTabPage
       // 
+      this.byTableTabPage.BackColor = System.Drawing.SystemColors.Window;
       this.byTableTabPage.Controls.Add(this.ertTableView);
       this.byTableTabPage.Location = new System.Drawing.Point(4, 22);
@@ -414,5 +417,5 @@
       this.byTableTabPage.TabIndex = 2;
       this.byTableTabPage.Text = "Expected Runtime Tables";
-      this.byTableTabPage.UseVisualStyleBackColor = true;
+      this.byTableTabPage.UseVisualStyleBackColor = false;
       // 
       // ertTableView
@@ -431,5 +434,5 @@
       // problemComboBox
       // 
-      this.problemComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+      this.problemComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
             | System.Windows.Forms.AnchorStyles.Right)));
       this.problemComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
Index: /stable/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.cs
===================================================================
--- /stable/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.cs	(revision 16835)
+++ /stable/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionRLDView.cs	(revision 16836)
@@ -21,4 +21,5 @@
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.ComponentModel;
@@ -30,4 +31,5 @@
 using HeuristicLab.Analysis;
 using HeuristicLab.Collections;
+using HeuristicLab.Common;
 using HeuristicLab.Core;
 using HeuristicLab.Core.Views;
@@ -78,4 +80,6 @@
     }
 
+    private List<AlgorithmInstance> groups;
+
     private double[] targets;
     private double[] budgets;
@@ -84,5 +88,6 @@
     private readonly BindingList<ProblemInstance> problems;
 
-    private bool suppressUpdates;
+    private bool updateInProgress;
+    private bool suppressContentEvents;
     private readonly IndexedDataTable<double> byCostDataTable;
     public IndexedDataTable<double> ByCostDataTable {
@@ -94,23 +99,25 @@
       invisibleTargetSeries = new List<Series>();
 
-      targetChart.CustomizeAllChartAreas();
-      targetChart.ChartAreas[0].CursorX.Interval = 1;
-      targetChart.SuppressExceptions = true;
-      byCostDataTable = new IndexedDataTable<double>("ECDF by Cost", "A data table containing the ECDF of function values (relative to best-known).") {
-        VisualProperties = {
-          YAxisTitle = "Proportion of runs",
-          YAxisMinimumFixedValue = 0,
-          YAxisMinimumAuto = false,
-          YAxisMaximumFixedValue = 1,
-          YAxisMaximumAuto = false
-        }
-      };
-      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;
+      try {
+        updateInProgress = true;
+        targetChart.CustomizeAllChartAreas();
+        targetChart.ChartAreas[0].CursorX.Interval = 1;
+        targetChart.SuppressExceptions = true;
+        byCostDataTable = new IndexedDataTable<double>("ECDF by Cost", "A data table containing the ECDF of function values (relative to best-known).") {
+          VisualProperties = {
+            YAxisTitle = "Proportion of runs",
+            YAxisMinimumFixedValue = 0,
+            YAxisMinimumAuto = false,
+            YAxisMaximumFixedValue = 1,
+            YAxisMaximumAuto = false
+          }
+        };
+        byCostViewHost.Content = byCostDataTable;
+
+        relativeOrAbsoluteComboBox.SelectedItem = targetsAreRelative ? "relative" : "absolute";
+        problems = new BindingList<ProblemInstance>();
+        problemComboBox.DataSource = new BindingSource() { DataSource = problems };
+        problemComboBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
+      } finally { updateInProgress = false; }
     }
 
@@ -134,35 +141,45 @@
 
     private void Content_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IRun> e) {
-      if (suppressUpdates) return;
+      foreach (var run in e.Items) RegisterRunEvents(run);
+      if (suppressContentEvents) return;
       if (InvokeRequired) {
         Invoke(new CollectionItemsChangedEventHandler<IRun>(Content_ItemsAdded), sender, e);
         return;
       }
-      UpdateGroupAndProblemComboBox();
-      UpdateDataTableComboBox();
-      foreach (var run in e.Items)
-        RegisterRunEvents(run);
+      if (updateInProgress) return;
+      try {
+        updateInProgress = true;
+        UpdateComboBoxes();
+        GroupRuns();
+      } finally { updateInProgress = false; }
     }
     private void Content_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IRun> e) {
-      if (suppressUpdates) return;
+      foreach (var run in e.Items) DeregisterRunEvents(run);
+      if (suppressContentEvents) return;
       if (InvokeRequired) {
         Invoke(new CollectionItemsChangedEventHandler<IRun>(Content_ItemsRemoved), sender, e);
         return;
       }
-      UpdateGroupAndProblemComboBox();
-      UpdateDataTableComboBox();
-      foreach (var run in e.Items)
-        DeregisterRunEvents(run);
+      if (updateInProgress) return;
+      try {
+        updateInProgress = true;
+        UpdateComboBoxes();
+        GroupRuns();
+      } finally { updateInProgress = false; }
     }
     private void Content_CollectionReset(object sender, CollectionItemsChangedEventArgs<IRun> e) {
-      if (suppressUpdates) return;
+      foreach (var run in e.OldItems) DeregisterRunEvents(run);
+      foreach (var run in e.Items) RegisterRunEvents(run);
+      if (suppressContentEvents) return;
       if (InvokeRequired) {
         Invoke(new CollectionItemsChangedEventHandler<IRun>(Content_CollectionReset), sender, e);
         return;
       }
-      UpdateGroupAndProblemComboBox();
-      UpdateDataTableComboBox();
-      foreach (var run in e.OldItems)
-        DeregisterRunEvents(run);
+      if (updateInProgress) return;
+      try {
+        updateInProgress = true;
+        UpdateComboBoxes();
+        GroupRuns();
+      } finally { updateInProgress = false; }
     }
     private void Content_AlgorithmNameChanged(object sender, EventArgs e) {
@@ -176,9 +193,12 @@
         return;
       }
-      suppressUpdates = Content.UpdateOfRunsInProgress;
-      if (!suppressUpdates) {
-        UpdateDataTableComboBox();
-        UpdateGroupAndProblemComboBox();
-        UpdateRuns();
+      suppressContentEvents = Content.UpdateOfRunsInProgress;
+      if (!suppressContentEvents) {
+        if (updateInProgress) return;
+        try {
+          updateInProgress = true;
+          UpdateComboBoxes();
+          GroupRuns();
+        } finally { updateInProgress = false; }
       }
     }
@@ -191,10 +211,15 @@
     }
     private void run_PropertyChanged(object sender, PropertyChangedEventArgs e) {
-      if (suppressUpdates) return;
+      if (suppressContentEvents) return;
       if (InvokeRequired) {
         Invoke((Action<object, PropertyChangedEventArgs>)run_PropertyChanged, sender, e);
       } else {
-        if (e.PropertyName == "Visible")
-          UpdateRuns();
+        if (e.PropertyName == "Visible") {
+          if (updateInProgress) return;
+          try {
+            updateInProgress = true;
+            UpdateRuns();
+          } finally { updateInProgress = false; }
+        }
       }
     }
@@ -213,11 +238,14 @@
       UpdateCaption();
       if (Content != null) {
-        UpdateGroupAndProblemComboBox();
-        UpdateDataTableComboBox();
-      }
-    }
-
-
-    private void UpdateGroupAndProblemComboBox() {
+        try {
+          updateInProgress = true;
+          UpdateComboBoxes();
+          UpdateRuns();
+        } finally { updateInProgress = false; }
+      }
+    }
+
+
+    private void UpdateComboBoxes() {
       var selectedGroupItem = (string)groupComboBox.SelectedItem;
 
@@ -232,35 +260,5 @@
       }
 
-      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 = (ProblemInstance)problemComboBox.SelectedItem;
-      problemComboBox.DataSource = null;
-      problemComboBox.Items.Clear();
-      problems.Clear();
-      problems.Add(ProblemInstance.MatchAll);
-      problemComboBox.DataSource = new BindingSource() { DataSource = problems };
-      problemComboBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
-      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();
-    }
-
-    private void UpdateDataTableComboBox() {
-      string selectedItem = (string)dataTableComboBox.SelectedItem;
+      string selectedDataTable = (string)dataTableComboBox.SelectedItem;
 
       dataTableComboBox.Items.Clear();
@@ -271,9 +269,22 @@
 
       dataTableComboBox.Items.AddRange(dataTables);
-      if (selectedItem != null && dataTableComboBox.Items.Contains(selectedItem)) {
-        dataTableComboBox.SelectedItem = selectedItem;
+      if (selectedDataTable != null && dataTableComboBox.Items.Contains(selectedDataTable)) {
+        dataTableComboBox.SelectedItem = selectedDataTable;
       } else if (dataTableComboBox.Items.Count > 0) {
         dataTableComboBox.SelectedItem = dataTableComboBox.Items[0];
       }
+
+      var selectedProblemItem = (ProblemInstance)problemComboBox.SelectedItem;
+      
+      UpdateProblemInstances();
+      
+      foreach (var p in problems) {
+        if (p.Equals(selectedProblemItem))
+          problemComboBox.SelectedItem = p;
+      }
+
+      if (selectedProblemItem == null && problems.Count > 1) problemComboBox.SelectedItem = problems[1];
+
+      SetEnabledStateOfControls();
     }
 
@@ -288,24 +299,61 @@
     }
 
-    private IEnumerable<AlgorithmInstance> GroupRuns() {
+    private void GroupRuns() {
+      groups = new List<AlgorithmInstance>();
+
       var table = (string)dataTableComboBox.SelectedItem;
-      if (string.IsNullOrEmpty(table)) yield break;
+      if (string.IsNullOrEmpty(table)) return;
 
       var selectedGroup = (string)groupComboBox.SelectedItem;
-      if (string.IsNullOrEmpty(selectedGroup)) yield break;
+      if (string.IsNullOrEmpty(selectedGroup)) return;
 
       var selectedProblem = (ProblemInstance)problemComboBox.SelectedItem;
-      if (selectedProblem == null) yield break;
-      
-      foreach (var x in (from r in Content
-                         where (selectedGroup == AllInstances || r.Parameters.ContainsKey(selectedGroup))
-                           && selectedProblem.Match(r)
-                           && r.Results.ContainsKey(table)
-                           && r.Visible
-                         let key = selectedGroup == AllInstances ? AllInstances : r.Parameters[selectedGroup].ToString()
-                         group r by key into g
-                         select new AlgorithmInstance(g.Key, g, problems))) {
-        yield return x;
-      }
+      if (selectedProblem == null) return;
+
+      foreach (var alg in from r in Content
+                          where (selectedGroup == AllInstances || r.Parameters.ContainsKey(selectedGroup))
+                            && r.Visible
+                            && selectedProblem.Match(r)
+                          let key = selectedGroup == AllInstances
+                            ? AllInstances : r.Parameters[selectedGroup].ToString()
+                          group r by key into g
+                          select g) {
+        var trials = (from run in alg
+                     from pd in problems.Skip(1) // exclude artificial match all
+                     where pd.Match(run) && run.Results.ContainsKey(table)
+                     let cgraph = run.Results[table] as IndexedDataTable<double>
+                     where cgraph != null && cgraph.Rows.Count > 0
+                        && cgraph.Rows.First().Values.Count > 0
+                     group cgraph by pd into g
+                     select g).ToList();
+
+        if (trials.Count == 0) continue;
+        groups.Add(new AlgorithmInstance(alg.Key, trials));
+      }
+    }
+
+    private void UpdateProblemInstances() {
+      try {
+        problems.Clear();
+        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;
+
+        problems.Add(ProblemInstance.MatchAll);
+        foreach (var p in problemDict.OrderBy(x => x.Key.ProblemName, new NaturalStringComparer()).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);
+        }
+      } finally { ((BindingSource)problemComboBox.DataSource).ResetBindings(false); }
     }
 
@@ -321,7 +369,6 @@
 
       if (targets == null) GenerateDefaultTargets();
-
-      var algInstances = GroupRuns().ToList();
-      if (algInstances.Count == 0) return;
+      
+      if (groups.Count == 0) return;
 
       var xAxisTitles = new HashSet<string>();
@@ -337,5 +384,5 @@
       var aggregate = aggregateTargetsCheckBox.Checked;
       double minEff = double.MaxValue, maxEff = double.MinValue;
-      foreach (var alg in algInstances) {
+      foreach (var alg in groups) {
         var noRuns = 0;
         SortedList<double, int> epdfHits = null, epdfMisses = null;
@@ -344,13 +391,12 @@
           misses[alg.Name] = epdfMisses = new SortedList<double, int>();
         }
-        foreach (var problem in alg.GetProblemInstances()) {
-          var max = problem.IsMaximization();
+        foreach (var problem in alg.GetCases().Where(x => x.Maximization.HasValue)) {
+          var max = problem.Maximization.Value;
           var absTargets = GetAbsoluteTargets(problem).ToArray();
-          foreach (var run in alg.GetRuns(problem)) {
+          foreach (var run in alg.GetTrials(problem)) {
             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();
+            xAxisTitles.Add(run.XAxisName);
+
+            var efforts = absTargets.Select(t => GetEffortToHitTarget(run, t, max)).ToArray();
             minEff = Math.Min(minEff, efforts.Min(x => x.Item2));
             maxEff = Math.Max(maxEff, efforts.Max(x => x.Item2));
@@ -391,5 +437,5 @@
       targetChart.ChartAreas[0].CursorY.Interval = 0.05;
 
-      UpdateErtTables(algInstances);
+      UpdateErtTables();
     }
 
@@ -401,5 +447,5 @@
       foreach (var list in hits) {
         var row = new Series(list.Key) {
-          ChartType = SeriesChartType.StepLine,
+          ChartType = list.Value.Count > 1000 ? SeriesChartType.FastLine : SeriesChartType.StepLine,
           BorderWidth = 3,
           Color = colors[colorCount],
@@ -423,5 +469,5 @@
         foreach (var h in list.Value) {
           var prevmissedecdf = missedecdf;
-          while (moreMisses && iter.Current.Key <= h.Key) {
+          while (moreMisses && iter.Current.Key < h.Key) {
             if (!labelPrinted && row.Points.Count > 0) {
               var point = row.Points.Last();
@@ -438,9 +484,6 @@
             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])
-              };
+              var dp = new DataPoint(iter.Current.Key, ecdf / movingTargets);
               if (showMarkers) {
                 dp.MarkerStyle = MarkerStyle.Circle;
@@ -470,9 +513,6 @@
           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])
-            };
+            var dp = new DataPoint(h.Key, ecdf / movingTargets);
             if (showMarkers) {
               dp.MarkerStyle = MarkerStyle.Circle;
@@ -492,15 +532,15 @@
           // 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}));
+          if (row.Points.Count == 0 || row.Points.Last().XValue < iter.Current.Key) {
+            var dp = new DataPoint(iter.Current.Key, ecdf / movingTargets);
+            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();
@@ -517,5 +557,5 @@
         }
 
-        if (!labelPrinted) {
+        if (!labelPrinted && row.Points.Count > 0) {
           var point = row.Points.Last();
           if (showLabelsInTargetChart)
@@ -554,5 +594,7 @@
     private IEnumerable<double> GetAbsoluteTargets(ProblemInstance pInstance) {
       if (!targetsAreRelative) return targets;
-      var maximization = pInstance.IsMaximization();
+      if (!pInstance.Maximization.HasValue) throw new ArgumentException("Problem doesn't specify if it is to be maximized or minimized.");
+
+      var maximization = pInstance.Maximization.Value;
       var bestKnown = pInstance.BestKnownQuality;
       if (double.IsNaN(bestKnown)) throw new ArgumentException("Problem instance does not have a defined best - known quality.");
@@ -570,7 +612,8 @@
 
     private double[] GetAbsoluteTargetsWorstToBest(ProblemInstance pInstance) {
-      if (double.IsNaN(pInstance.BestKnownQuality)) throw new ArgumentException("Problem instance does not have a defined best-known quality.");
+      if (targetsAreRelative && double.IsNaN(pInstance.BestKnownQuality)) throw new ArgumentException("Problem instance does not have a defined best-known quality.");
+      if (!pInstance.Maximization.HasValue) throw new ArgumentException("Problem doesn't specify if it is to be maximized or minimized.");
       var absTargets = GetAbsoluteTargets(pInstance);
-      return (pInstance.IsMaximization()
+      return (pInstance.Maximization.Value
         ? absTargets.OrderBy(x => x) : absTargets.OrderByDescending(x => x)).ToArray();
     }
@@ -582,24 +625,24 @@
 
     private Tuple<bool, double> GetEffortToHitTarget(
-        ObservableList<Tuple<double, double>> convergenceGraph,
+        ConvergenceGraph cgraph,
         double absTarget, bool maximization) {
-      if (convergenceGraph.Count == 0)
+      if (cgraph.Points == 0)
         throw new ArgumentException("Convergence graph is empty.", "convergenceGraph");
       
-      var index = convergenceGraph.BinarySearch(Tuple.Create(0.0, absTarget), new TargetComparer(maximization));
+      var index = cgraph.BinarySearch(new ConvergenceGraphPoint(0.0, absTarget), new TargetComparer(maximization));
       if (index >= 0) {
-        return Tuple.Create(true, convergenceGraph[index].Item1);
+        return Tuple.Create(true, cgraph[index].Runlength);
       } else {
         index = ~index;
-        if (index >= convergenceGraph.Count)
-          return Tuple.Create(false, convergenceGraph.Last().Item1);
-        return Tuple.Create(true, convergenceGraph[index].Item1);
-      }
-    }
-
-    private void UpdateErtTables(List<AlgorithmInstance> algorithmInstances) {
+        if (index >= cgraph.Points)
+          return Tuple.Create(false, cgraph.Last().Runlength);
+        return Tuple.Create(true, cgraph[index].Runlength);
+      }
+    }
+
+    private void UpdateErtTables() {
       ertTableView.Content = null;
       var columns = targets.Length + 1;
-      var totalRows = algorithmInstances.Count * algorithmInstances.Max(x => x.GetNumberOfProblemInstances()) + algorithmInstances.Max(x => x.GetNumberOfProblemInstances());
+      var totalRows = groups.Count * groups.Max(x => x.GetNumberOfCases()) + groups.Max(x => x.GetNumberOfCases());
       var matrix = new StringMatrix(totalRows, columns);
       var rowNames = new List<string>();
@@ -611,9 +654,10 @@
       if (string.IsNullOrEmpty(tableName)) return;
       
-      var problems = algorithmInstances.SelectMany(x => x.GetProblemInstances()).Distinct().ToList();
-
-      foreach (var problem in problems) {
-        var max = problem.IsMaximization();
+      var problems = groups.SelectMany(x => x.GetCases()).Distinct().ToList();
+
+      foreach (var problem in problems.OrderBy(x => x.ProblemName, new NaturalStringComparer())) {
+        if (double.IsNaN(problem.BestKnownQuality) || !problem.Maximization.HasValue) continue;
         rowNames.Add(problem.ToString());
+        var max = problem.Maximization.Value;
         var absTargets = GetAbsoluteTargetsWorstToBest(problem);
         if (targetsAreRelative) {
@@ -625,7 +669,7 @@
         rowCount++;
 
-        foreach (var alg in algorithmInstances) {
+        foreach (var alg in groups) {
           rowNames.Add(alg.Name);
-          var runs = alg.GetRuns(problem).ToList();
+          var runs = alg.GetTrials(problem).ToList();
           if (runs.Count == 0) {
             matrix[rowCount, columns - 1] = "N/A";
@@ -635,5 +679,5 @@
           var result = default(ErtCalculationResult);
           for (var i = 0; i < absTargets.Length; i++) {
-            result = ExpectedRuntimeHelper.CalculateErt(runs, tableName, absTargets[i], max);
+            result = ExpectedRuntimeHelper.CalculateErt(runs.Select(x => x.ToTuples()), absTargets[i], max);
             matrix[rowCount, i] = result.ToString();
           }
@@ -658,19 +702,16 @@
 
       if (budgets == null) GenerateDefaultBudgets(table);
-
-      var algInstances = GroupRuns().ToList();
-      if (algInstances.Count == 0) return;
+      
+      if (groups.Count == 0) return;
 
       var colorCount = 0;
       var lineStyleCount = 0;
       
-      foreach (var alg in algInstances) {
+      foreach (var alg in groups) {
         var hits = new Dictionary<string, SortedList<double, double>>();
 
-        foreach (var problem in alg.GetProblemInstances()) {
-          foreach (var run in alg.GetRuns(problem)) {
-            var resultsTable = (IndexedDataTable<double>)run.Results[table];
-
-            CalculateHitsForEachBudget(hits, resultsTable.Rows.First(), problem, alg.Name);
+        foreach (var problem in alg.GetCases()) {
+          foreach (var run in alg.GetTrials(problem)) {
+            CalculateHitsForEachBudget(hits, run, problem, alg.Name);
           }
         }
@@ -705,7 +746,19 @@
 
     private void GenerateDefaultBudgets(string table) {
-      var runs = Content;
-      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 runs = Content.Where(x => x.Results.ContainsKey(table) && x.Results[table] is IndexedDataTable<double>)
+                        .Select(x => (IndexedDataTable<double>)x.Results[table])
+                        .Where(x => x.Rows.Count > 0 && x.Rows.First().Values.Count > 0)
+                        .Select(x => x.Rows.First())
+                        .ToList();
+      if (runs.Count == 0) {
+        budgets = new double[0];
+        suppressBudgetsEvents = true;
+        budgetsTextBox.Text = string.Empty;
+        suppressBudgetsEvents = false;
+        return;
+      }
+
+      var min = runs.Select(x => x.Values.Select(y => y.Item1).Min()).Min();
+      var max = runs.Select(x => x.Values.Select(y => y.Item1).Max()).Max();
       var points = 3;
       budgets = Enumerable.Range(1, points).Select(x => min + (x / (double)points) * (max - min)).ToArray();
@@ -715,17 +768,17 @@
     }
 
-    private void CalculateHitsForEachBudget(Dictionary<string, SortedList<double, double>> hits, IndexedDataRow<double> row, ProblemInstance problem, string groupName) {
-      var max = problem.IsMaximization();
+    private void CalculateHitsForEachBudget(Dictionary<string, SortedList<double, double>> hits, ConvergenceGraph cgraph, ProblemInstance problem, string groupName) {
+      var max = problem.Maximization.Value;
       var prevIndex = 0;
       foreach (var b in budgets) {
         var key = groupName + "-" + b;
-        var index = row.Values.BinarySearch(prevIndex, row.Values.Count - prevIndex, Tuple.Create(b, 0.0), new CostComparer());
+        var index = cgraph.BinarySearch(prevIndex, cgraph.Points - prevIndex, new ConvergenceGraphPoint(b, 0.0), new CostComparer());
         if (index < 0) {
           index = ~index;
-          if (index >= row.Values.Count) break; // the run wasn't long enough to use up budget b (or any subsequent larger one)
+          if (index >= cgraph.Points) break; // the run wasn't long enough to use up budget b (or any subsequent larger one)
         }
         if (!hits.ContainsKey(key)) hits.Add(key, new SortedList<double, double>());
-        var v = row.Values[index];
-        var relTgt = CalculateRelativeDifference(max, problem.BestKnownQuality, v.Item2) + 1;
+        var v = cgraph[index];
+        var relTgt = CalculateRelativeDifference(max, problem.BestKnownQuality, v.Quality) + 1;
         if (hits[key].ContainsKey(relTgt))
           hits[key][relTgt]++;
@@ -743,27 +796,36 @@
       if (InvokeRequired) Invoke((Action)SynchronizeTargetTextBox);
       else {
-        suppressTargetsEvents = true;
-        try {
-          if (targetsAreRelative)
-            targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%";
-          else targetsTextBox.Text = string.Join(" ; ", targets);
-        } finally { suppressTargetsEvents = false; }
+        if (targetsAreRelative)
+          targetsTextBox.Text = string.Join("% ; ", targets.Select(x => x * 100)) + "%";
+        else targetsTextBox.Text = string.Join(" ; ", targets);
       }
     }
 
     private void groupComboBox_SelectedIndexChanged(object sender, EventArgs e) {
-      UpdateRuns();
-      SetEnabledStateOfControls();
+      if (updateInProgress) return;
+      try {
+        updateInProgress = true;
+        UpdateRuns();
+        SetEnabledStateOfControls();
+      } finally { updateInProgress = false; }
     }
     private void problemComboBox_SelectedIndexChanged(object sender, EventArgs e) {
-      UpdateRuns();
-      SetEnabledStateOfControls();
+      if (updateInProgress) return;
+      try {
+        updateInProgress = true;
+        UpdateRuns();
+        SetEnabledStateOfControls();
+      } finally { updateInProgress = false; }
     }
     private void dataTableComboBox_SelectedIndexChanged(object sender, EventArgs e) {
-      if (dataTableComboBox.SelectedIndex >= 0)
-        GenerateDefaultBudgets((string)dataTableComboBox.SelectedItem);
-      UpdateBestKnownQualities();
-      UpdateRuns();
-      SetEnabledStateOfControls();
+      if (updateInProgress) return;
+      try {
+        updateInProgress = true;
+        if (dataTableComboBox.SelectedIndex >= 0)
+          GenerateDefaultBudgets((string)dataTableComboBox.SelectedItem);
+        UpdateBestKnownQualities();
+        UpdateRuns();
+        SetEnabledStateOfControls();
+      } finally { updateInProgress = false; }
     }
 
@@ -778,63 +840,75 @@
 
     #region Event handlers for target analysis
-    private bool suppressTargetsEvents;
     private void targetsTextBox_Validating(object sender, CancelEventArgs e) {
-      if (suppressTargetsEvents) return;
-      var targetStrings = targetsTextBox.Text.Split(new[] { '%', ';', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
-      var targetList = new List<decimal>();
-      foreach (var ts in targetStrings) {
-        decimal t;
-        if (!decimal.TryParse(ts, out t)) {
-          errorProvider.SetError(targetsTextBox, "Not all targets can be parsed: " + ts);
+      if (updateInProgress) return;
+      try {
+        updateInProgress = true;
+        var targetStrings = targetsTextBox.Text.Split(new[] { '%', ';', '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
+        var targetList = new List<decimal>();
+        foreach (var ts in targetStrings) {
+          decimal t;
+          if (!decimal.TryParse(ts, out t)) {
+            errorProvider.SetError(targetsTextBox, "Not all targets can be parsed: " + ts);
+            e.Cancel = true;
+            return;
+          }
+          if (targetsAreRelative)
+            targetList.Add(t / 100);
+          else targetList.Add(t);
+        }
+        if (targetList.Count == 0) {
+          errorProvider.SetError(targetsTextBox, "Give at least one target value!");
           e.Cancel = true;
           return;
         }
-        if (targetsAreRelative)
-          targetList.Add(t / 100);
-        else targetList.Add(t);
-      }
-      if (targetList.Count == 0) {
-        errorProvider.SetError(targetsTextBox, "Give at least one target value!");
-        e.Cancel = true;
-        return;
-      }
-      e.Cancel = false;
-      errorProvider.SetError(targetsTextBox, null);
-      targets = targetsAreRelative ? targetList.Select(x => (double)x).OrderByDescending(x => x).ToArray() : targetList.Select(x => (double)x).ToArray();
-
-      SynchronizeTargetTextBox();
-      UpdateResultsByTarget();
-      SetEnabledStateOfControls();
+        e.Cancel = false;
+        errorProvider.SetError(targetsTextBox, null);
+        targets = targetsAreRelative ? targetList.Select(x => (double)x).OrderByDescending(x => x).ToArray() : targetList.Select(x => (double)x).ToArray();
+
+        SynchronizeTargetTextBox();
+        UpdateResultsByTarget();
+        SetEnabledStateOfControls();
+
+      } finally { updateInProgress = false; }
     }
 
     private void aggregateTargetsCheckBox_CheckedChanged(object sender, EventArgs e) {
-      SuspendRepaint();
+      if (updateInProgress) return;
       try {
-        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";
-      SynchronizeTargetTextBox();
-
-      try {
+        updateInProgress = true;
         SuspendRepaint();
         UpdateResultsByTarget();
-      } finally { ResumeRepaint(true); }
+      } finally {
+        updateInProgress = false;
+        ResumeRepaint(true);
+      }
+    }
+
+    private void relativeOrAbsoluteComboBox_SelectedIndexChanged(object sender, EventArgs e) {
+      if (updateInProgress) return;
+      try {
+        updateInProgress = true;
+        var pd = (ProblemInstance)problemComboBox.SelectedItem;
+        if (!double.IsNaN(pd.BestKnownQuality) && pd.Maximization.HasValue) {
+          var max = pd.Maximization.Value;
+          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";
+        SynchronizeTargetTextBox();
+        
+        SuspendRepaint();
+        UpdateResultsByTarget();
+      } finally {
+        updateInProgress = false;
+        ResumeRepaint(true);
+      }
     }
 
@@ -856,7 +930,10 @@
               : dialog.Values.Select(x => (double)x).ToArray();
 
-            SynchronizeTargetTextBox();
-            UpdateResultsByTarget();
-            SetEnabledStateOfControls();
+            try {
+              updateInProgress = true;
+              SynchronizeTargetTextBox();
+              UpdateResultsByTarget();
+              SetEnabledStateOfControls();
+            } finally { updateInProgress = false; }
           }
         }
@@ -869,16 +946,20 @@
       
       foreach (var run in Content) {
-        if (!run.Results.ContainsKey(table)) continue;
+        if (!run.Results.ContainsKey(table) || !(run.Results[table] is IndexedDataTable<double>)) continue;
         var resultsTable = (IndexedDataTable<double>)run.Results[table];
         var values = resultsTable.Rows.First().Values;
         var pd = new ProblemInstance(run);
+        if (!pd.Maximization.HasValue) continue;
         pd = problems.Single(x => x.Equals(pd));
-        var max = pd.IsMaximization();
+        if (targetsAreRelative && double.IsNaN(pd.BestKnownQuality)) continue;
+
+        var max = pd.Maximization.Value;
         var absTargets = GetAbsoluteTargetsWorstToBest(pd);
+        var cgraph = new ConvergenceGraph(resultsTable, max);
 
         var prevIndex = 0;
         for (var i = 0; i < absTargets.Length; i++) {
           var absTarget = absTargets[i];
-          var index = values.BinarySearch(prevIndex, values.Count - prevIndex, Tuple.Create(0.0, absTarget), new TargetComparer(max));
+          var index = cgraph.BinarySearch(prevIndex, values.Count - prevIndex, new ConvergenceGraphPoint(0.0, absTarget), new TargetComparer(max));
           if (index < 0) {
             index = ~index;
@@ -893,16 +974,24 @@
 
     private void markerCheckBox_CheckedChanged(object sender, EventArgs e) {
-      SuspendRepaint();
       try {
+        updateInProgress = true;
+        SuspendRepaint();
         UpdateResultsByTarget();
-      } finally { ResumeRepaint(true); }
+      } finally {
+        updateInProgress = false;
+        ResumeRepaint(true);
+      }
     }
 
     private void showLabelsCheckBox_CheckedChanged(object sender, EventArgs e) {
       showLabelsInTargetChart = showLabelsCheckBox.Checked;
-      SuspendRepaint();
       try {
+        updateInProgress = true;
+        SuspendRepaint();
         UpdateResultsByTarget();
-      } finally { ResumeRepaint(true); }
+      } finally {
+        updateInProgress = false;
+        ResumeRepaint(true);
+      }
     }
     #endregion
@@ -976,9 +1065,11 @@
         var values = resultsTable.Rows.First().Values;
         var pd = new ProblemInstance(run);
+        if (!pd.Maximization.HasValue) continue;
         pd = problems.Single(x => x.Equals(pd));
-
+        var max = pd.Maximization.Value;
+        var cgraph = new ConvergenceGraph(resultsTable, max);
         var prevIndex = 0;
         foreach (var b in budgets) {
-          var index = values.BinarySearch(prevIndex, values.Count - prevIndex, Tuple.Create(b, 0.0), new CostComparer());
+          var index = cgraph.BinarySearch(prevIndex, values.Count - prevIndex, new ConvergenceGraphPoint(b, 0.0), new CostComparer());
           if (index < 0) {
             index = ~index;
@@ -986,5 +1077,5 @@
           }
           var v = values[index];
-          var tgt = targetsAreRelative ? CalculateRelativeDifference(pd.IsMaximization(), pd.BestKnownQuality, v.Item2) : v.Item2;
+          var tgt = targetsAreRelative ? CalculateRelativeDifference(max, pd.BestKnownQuality, v.Item2) : v.Item2;
           run.Results[table + (targetsAreRelative ? ".CostForRelTarget " : ".CostForAbsTarget ") + b] = new DoubleValue(tgt);
           prevIndex = index;
@@ -1011,16 +1102,35 @@
     private Dictionary<ProblemInstance, double> CalculateBestTargetPerProblemInstance(string table) {
       if (table == null) table = string.Empty;
-      return (from r in Content
-              where r.Visible
-              let pd = new ProblemInstance(r)
-              let target = r.Parameters.ContainsKey("BestKnownQuality")
-                           && r.Parameters["BestKnownQuality"] is DoubleValue
-                ? ((DoubleValue)r.Parameters["BestKnownQuality"]).Value
-                : (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.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);
+      var dict = new Dictionary<ProblemInstance, double>();
+      foreach (var k in from r in Content where r.Visible let pd = new ProblemInstance(r) group r by pd into g select new { Problem = g.Key, Runs = g.ToList() }) {
+        var pd = k.Problem;
+        if (!pd.Maximization.HasValue) continue;
+        var values = GetQualityValues(k.Runs, table).ToList();
+        var target = double.NaN;
+        if (values.Count > 0) {
+          target = pd.Maximization.Value ? values.Max() : values.Min();
+        }
+        dict[pd] = target;
+      }
+      return dict;
+    }
+
+    private IEnumerable<double> GetQualityValues(List<IRun> runs, string table) {
+      foreach (var r in runs) {
+        IItem item;
+        if (r.Parameters.TryGetValue("BestKnownQuality", out item)) {
+          var dval = item as DoubleValue;
+          if (dval != null && !double.IsNaN(dval.Value))
+            yield return dval.Value;
+        }
+        if (r.Results.TryGetValue(table, out item)) {
+          var dt = item as IndexedDataTable<double>;
+          if (dt != null && dt.Rows.Count > 0 && dt.Rows.First().Values.Count > 0) {
+            var last = dt.Rows.First().Values.Last().Item2;
+            if (!double.IsNaN(last))
+              yield return Math.Round(last, 10);
+          }
+        }
+      }
     }
 
@@ -1032,4 +1142,5 @@
       SuspendRepaint();
       try {
+        GroupRuns();
         UpdateResultsByTarget();
         UpdateResultsByCost();
@@ -1136,4 +1247,5 @@
     }
 
+    #region Helper classes
     private class AlgorithmInstance : INotifyPropertyChanged {
       private string name;
@@ -1147,42 +1259,40 @@
       }
 
-      private Dictionary<ProblemInstance, List<IRun>> performanceData;
-
-      public int GetNumberOfProblemInstances() {
+      private Dictionary<ProblemInstance, List<ConvergenceGraph>> performanceData;
+
+      public int GetNumberOfCases() {
         return performanceData.Count;
       }
 
-      public IEnumerable<ProblemInstance> GetProblemInstances() {
+      public IEnumerable<ProblemInstance> GetCases() {
         return performanceData.Keys;
       }
 
-      public int GetNumberOfRuns(ProblemInstance p) {
+      public int GetNumberOfTrials(ProblemInstance p) {
         if (p == ProblemInstance.MatchAll) return performanceData.Select(x => x.Value.Count).Sum();
-        List<IRun> runs;
-        if (performanceData.TryGetValue(p, out runs))
-          return runs.Count;
+        List<ConvergenceGraph> trials;
+        if (performanceData.TryGetValue(p, out trials))
+          return trials.Count;
 
         return 0;
       }
 
-      public IEnumerable<IRun> GetRuns(ProblemInstance p) {
+      public IEnumerable<ConvergenceGraph> GetTrials(ProblemInstance p) {
         if (p == ProblemInstance.MatchAll) return performanceData.SelectMany(x => x.Value);
-        List<IRun> runs;
-        if (performanceData.TryGetValue(p, out runs))
-          return runs;
-
-        return Enumerable.Empty<IRun>();
-      }
-
-      public AlgorithmInstance(string name, IEnumerable<IRun> runs, IEnumerable<ProblemInstance> problems) {
+        List<ConvergenceGraph> trials;
+        if (performanceData.TryGetValue(p, out trials))
+          return trials;
+
+        return Enumerable.Empty<ConvergenceGraph>();
+      }
+
+      public AlgorithmInstance(string name, IEnumerable<IGrouping<ProblemInstance, IndexedDataTable<double>>> trials) {
         this.name = name;
 
-        var pDict = problems.ToDictionary(r => r, _ => new List<IRun>());
-        foreach (var y in runs) {
-          var pd = new ProblemInstance(y);
-          List<IRun> l;
-          if (pDict.TryGetValue(pd, out l)) l.Add(y);
-        }
-        performanceData = pDict.Where(x => x.Value.Count > 0).ToDictionary(x => x.Key, x => x.Value);
+        performanceData = new Dictionary<ProblemInstance, List<ConvergenceGraph>>();
+        foreach (var t in trials) {
+          if (double.IsNaN(t.Key.BestKnownQuality) || !t.Key.Maximization.HasValue) continue;
+          performanceData[t.Key] = t.Select(c => new ConvergenceGraph(c, t.Key.Maximization.Value)).ToList();
+        }
       }
 
@@ -1214,5 +1324,5 @@
         ProblemName = string.Empty;
         Evaluator = string.Empty;
-        Maximization = string.Empty;
+        Maximization = null;
         DisplayProblemType = false;
         DisplayProblemName = false;
@@ -1227,9 +1337,9 @@
         ProblemName = GetStringValueOrEmpty(run, "Problem Name");
         Evaluator = GetStringValueOrEmpty(run, "Evaluator");
-        Maximization = GetMaximizationValueOrEmpty(run, "Maximization");
+        Maximization = GetBoolValueOrEmpty(run, "Maximization");
         DisplayProblemType = !string.IsNullOrEmpty(ProblemType);
         DisplayProblemName = !string.IsNullOrEmpty(ProblemName);
         DisplayEvaluator = !string.IsNullOrEmpty(Evaluator);
-        DisplayMaximization = !string.IsNullOrEmpty(Maximization);
+        DisplayMaximization = Maximization.HasValue;
         matchAll = false;
         BestKnownQuality = GetDoubleValueOrNaN(run, "BestKnownQuality");
@@ -1299,6 +1409,6 @@
         }
       }
-      private string maximization;
-      public string Maximization {
+      private bool? maximization;
+      public bool? Maximization {
         get { return maximization; }
         set {
@@ -1318,14 +1428,10 @@
       }
 
-      public bool IsMaximization() {
-        return Maximization == "MAX";
-      }
-
       public bool Match(IRun run) {
         return matchAll ||
-               GetStringValueOrEmpty(run, "Problem Type") == ProblemType
+               (GetStringValueOrEmpty(run, "Problem Type") == ProblemType
                && GetStringValueOrEmpty(run, "Problem Name") == ProblemName
                && GetStringValueOrEmpty(run, "Evaluator") == Evaluator
-               && GetMaximizationValueOrEmpty(run, "Maximization") == Maximization;
+               && GetBoolValueOrEmpty(run, "Maximization") == Maximization);
       }
 
@@ -1348,11 +1454,11 @@
       }
 
-      private string GetMaximizationValueOrEmpty(IRun run, string key) {
+      private bool? GetBoolValueOrEmpty(IRun run, string key) {
         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;
+          if (bv != null) return bv.Value;
+        }
+        return null;
       }
 
@@ -1375,5 +1481,5 @@
           (DisplayProblemName ? ProblemName : string.Empty),
           (DisplayEvaluator ? Evaluator : string.Empty),
-          (DisplayMaximization ? Maximization : string.Empty),
+          (DisplayMaximization && Maximization.HasValue ? (Maximization.Value ? "MAX" : "MIN") : string.Empty),
           !double.IsNaN(BestKnownQuality) ? BestKnownQuality.ToString(CultureInfo.CurrentCulture.NumberFormat) : string.Empty }.Where(x => !string.IsNullOrEmpty(x)));
       }
@@ -1386,11 +1492,11 @@
     }
 
-    private class CostComparer : Comparer<Tuple<double, double>> {
-      public override int Compare(Tuple<double, double> x, Tuple<double, double> y) {
-        return x.Item1.CompareTo(y.Item1);
-      }
-    }
-
-    private class TargetComparer : Comparer<Tuple<double, double>> {
+    private class CostComparer : Comparer<ConvergenceGraphPoint> {
+      public override int Compare(ConvergenceGraphPoint x, ConvergenceGraphPoint y) {
+        return x.Runlength.CompareTo(y.Runlength);
+      }
+    }
+
+    private class TargetComparer : Comparer<ConvergenceGraphPoint> {
       public bool Maximization { get; private set; }
       public TargetComparer(bool maximization) {
@@ -1398,8 +1504,84 @@
       }
 
-      public override int Compare(Tuple<double, double> x, Tuple<double, double> y) {
-        return Maximization ? x.Item2.CompareTo(y.Item2) : y.Item2.CompareTo(x.Item2);
-      }
-    }
+      public override int Compare(ConvergenceGraphPoint x, ConvergenceGraphPoint y) {
+        return Maximization ? x.Quality.CompareTo(y.Quality) : y.Quality.CompareTo(x.Quality);
+      }
+    }
+
+    private class ConvergenceGraph : IEnumerable<ConvergenceGraphPoint> {
+      private List<ConvergenceGraphPoint> data;
+      private bool maximization;
+      private string xAxisLabel;
+
+      public string XAxisName { get { return xAxisLabel; } }
+
+      public int Points {
+        get { return data.Count; }
+      }
+
+      public double TotalRunlength {
+        get { return data.Last().Runlength; }
+      }
+
+      public double BestQuality {
+        get { return data.Last().Quality; }
+      }
+
+      public double QualityAt(double runlength) {
+        var point = data.SkipWhile(x => x.Runlength < runlength).FirstOrDefault();
+        if (point == null) return double.NaN;
+        return point.Quality;
+      }
+
+      public double RunlengthFor(double quality) {
+        var point = (maximization ? data.SkipWhile(x => x.Quality < quality) : data.SkipWhile(x => x.Quality > quality)).FirstOrDefault();
+        if (point == null) return double.NaN;
+        return point.Runlength;
+      }
+
+      public ConvergenceGraphPoint this[int point] {
+        get { return data[point]; }
+      }
+
+      public ConvergenceGraph(IndexedDataTable<double> table, bool maximization) {
+        data = table.Rows.First().Values.Select(x => new ConvergenceGraphPoint() { Runlength = x.Item1, Quality = x.Item2 }).ToList();
+        xAxisLabel = table.VisualProperties.XAxisTitle;
+        this.maximization = maximization;
+      }
+
+      public IEnumerator<ConvergenceGraphPoint> GetEnumerator() {
+        return data.GetEnumerator();
+      }
+
+      IEnumerator IEnumerable.GetEnumerator() {
+        return data.GetEnumerator();
+      }
+
+      public int BinarySearch(ConvergenceGraphPoint item, IComparer<ConvergenceGraphPoint> comparer) {
+        return data.BinarySearch(item, comparer);
+      }
+
+      public int BinarySearch(int index, int count, ConvergenceGraphPoint point, IComparer<ConvergenceGraphPoint> comparer) {
+        return data.BinarySearch(index, count, point, comparer);
+      }
+
+      public IEnumerable<Tuple<double, double>> ToTuples() {
+        return data.Select(x => Tuple.Create(x.Runlength, x.Quality));
+      }
+    }
+
+    private class ConvergenceGraphPoint {
+      public double Runlength { get; set; }
+      public double Quality { get; set; }
+
+      public ConvergenceGraphPoint() {
+
+      }
+      public ConvergenceGraphPoint(double runlength, double quality) {
+        Runlength = runlength;
+        Quality = quality;
+      }
+    }
+    #endregion
   }
 }
