Index: trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/DensityChart.cs
===================================================================
--- trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/DensityChart.cs	(revision 14267)
+++ trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/DensityChart.cs	(revision 14826)
@@ -30,10 +30,20 @@
     }
 
+    public void UpdateChart(IList<string> data, double minimumHeight = 0.1) {
+      if (data == null || !data.Any())
+        return;
+      UpdateChartWithBuckets(CalculateBuckets(data));
+    }
+
+
     public void UpdateChart(IList<double> data, double min, double max, int numBuckets, double minimumHeight = 0.1) {
       if (data == null || numBuckets < 0 || min > max || max < min)
         return;
 
-      var buckets = CalculateBuckets(data, numBuckets, min, max);
+      UpdateChartWithBuckets(CalculateBuckets(data, numBuckets, min, max));
+    }
 
+
+    private void UpdateChartWithBuckets(double[] buckets) {
       // set minimum height of all non-zero buckets on 10% of maximum
       double minHeight = buckets.Max() * 0.1;
@@ -69,4 +79,7 @@
       return buckets;
     }
+    private double[] CalculateBuckets(IList<string> data) {
+      return data.GroupBy(val => val).OrderBy(g => g.Key).Select(g => (double)g.Count()).Concat(new double[] { 0.0 }).ToArray();
+    }
   }
 }
Index: trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/FactorGradientChart.Designer.cs
===================================================================
--- trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/FactorGradientChart.Designer.cs	(revision 14826)
+++ trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/FactorGradientChart.Designer.cs	(revision 14826)
@@ -0,0 +1,159 @@
+﻿namespace HeuristicLab.Problems.DataAnalysis.Views {
+  partial class FactorGradientChart {
+    /// <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 Component 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.VerticalLineAnnotation verticalLineAnnotation1 = new System.Windows.Forms.DataVisualization.Charting.VerticalLineAnnotation();
+      System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
+      System.Windows.Forms.DataVisualization.Charting.StripLine stripLine1 = new System.Windows.Forms.DataVisualization.Charting.StripLine();
+      System.Windows.Forms.DataVisualization.Charting.StripLine stripLine2 = new System.Windows.Forms.DataVisualization.Charting.StripLine();
+      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.Series series2 = new System.Windows.Forms.DataVisualization.Charting.Series();
+      System.Windows.Forms.DataVisualization.Charting.Series series3 = new System.Windows.Forms.DataVisualization.Charting.Series();
+      System.Windows.Forms.DataVisualization.Charting.Series series4 = new System.Windows.Forms.DataVisualization.Charting.Series();
+      System.Windows.Forms.DataVisualization.Charting.Title title1 = new System.Windows.Forms.DataVisualization.Charting.Title();
+      this.calculationPendingLabel = new System.Windows.Forms.Label();
+      this.calculationPendingTimer = new System.Windows.Forms.Timer(this.components);
+      this.chart = new HeuristicLab.Visualization.ChartControlsExtensions.EnhancedChart();
+      this.toolTip = new System.Windows.Forms.ToolTip(this.components);
+      ((System.ComponentModel.ISupportInitialize)(this.chart)).BeginInit();
+      this.SuspendLayout();
+      // 
+      // calculationPendingLabel
+      // 
+      this.calculationPendingLabel.BackColor = System.Drawing.Color.White;
+      this.calculationPendingLabel.Image = HeuristicLab.Common.Resources.VSImageLibrary.Timer;
+      this.calculationPendingLabel.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
+      this.calculationPendingLabel.Location = new System.Drawing.Point(3, 3);
+      this.calculationPendingLabel.Margin = new System.Windows.Forms.Padding(0);
+      this.calculationPendingLabel.Name = "calculationPendingLabel";
+      this.calculationPendingLabel.Size = new System.Drawing.Size(17, 17);
+      this.calculationPendingLabel.TabIndex = 1;
+      this.calculationPendingLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+      this.calculationPendingLabel.Visible = false;
+      // 
+      // calculationPendingTimer
+      // 
+      this.calculationPendingTimer.Tick += new System.EventHandler(this.calculationPendingTimer_Tick);
+      // 
+      // chart
+      // 
+      this.chart.AllowDrop = true;
+      verticalLineAnnotation1.AllowMoving = true;
+      verticalLineAnnotation1.AxisXName = "ChartArea\\rX";
+      verticalLineAnnotation1.ClipToChartArea = "ChartArea";
+      verticalLineAnnotation1.IsInfinitive = true;
+      verticalLineAnnotation1.LineColor = System.Drawing.Color.Red;
+      verticalLineAnnotation1.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dash;
+      verticalLineAnnotation1.Name = "VerticalLineAnnotation";
+      verticalLineAnnotation1.YAxisName = "ChartArea\\rY";
+      this.chart.Annotations.Add(verticalLineAnnotation1);
+      chartArea1.AxisX.IsMarginVisible = false;
+      stripLine1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(223)))), ((int)(((byte)(58)))), ((int)(((byte)(2)))));
+      stripLine2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(223)))), ((int)(((byte)(58)))), ((int)(((byte)(2)))));
+      chartArea1.AxisX.StripLines.Add(stripLine1);
+      chartArea1.AxisX.StripLines.Add(stripLine2);
+      chartArea1.Name = "ChartArea";
+      chartArea1.Position.Auto = false;
+      chartArea1.Position.Height = 90F;
+      chartArea1.Position.Width = 100F;
+      chartArea1.Position.Y = 10F;
+      this.chart.ChartAreas.Add(chartArea1);
+      this.chart.Dock = System.Windows.Forms.DockStyle.Fill;
+      legend1.Alignment = System.Drawing.StringAlignment.Center;
+      legend1.Docking = System.Windows.Forms.DataVisualization.Charting.Docking.Top;
+      legend1.LegendItemOrder = System.Windows.Forms.DataVisualization.Charting.LegendItemOrder.ReversedSeriesOrder;
+      legend1.LegendStyle = System.Windows.Forms.DataVisualization.Charting.LegendStyle.Row;
+      legend1.Name = "Default";
+      this.chart.Legends.Add(legend1);
+      this.chart.Location = new System.Drawing.Point(0, 0);
+      this.chart.Name = "chart";
+      series1.BorderColor = System.Drawing.Color.Red;
+      series1.BorderDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dash;
+      series1.ChartArea = "ChartArea";
+      series1.IsVisibleInLegend = false;
+      series1.Legend = "Default";
+      series1.Name = "Series1";
+      series1.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.String;
+      series1.YValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Double;
+      series2.ChartArea = "ChartArea";
+      series2.IsVisibleInLegend = false;
+      series2.Legend = "Default";
+      series2.Name = "Series2";
+      series2.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.String;
+      series3.ChartArea = "ChartArea";
+      series3.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.BoxPlot;
+      series3.Color = System.Drawing.Color.Black;
+      series3.Legend = "Default";
+      series3.Name = "Series3";
+      series3.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.String;
+      series3.YValuesPerPoint = 6;
+      series4.ChartArea = "ChartArea";
+      series4.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.BoxPlot;
+      series4.Legend = "Default";
+      series4.Name = "Series4";
+      series4.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.String;
+      series4.YValuesPerPoint = 6;
+      this.chart.Series.Add(series1);
+      this.chart.Series.Add(series2);
+      this.chart.Series.Add(series3);
+      this.chart.Series.Add(series4);
+      this.chart.Size = new System.Drawing.Size(453, 308);
+      this.chart.TabIndex = 0;
+      title1.Alignment = System.Drawing.ContentAlignment.TopCenter;
+      title1.DockedToChartArea = "ChartArea";
+      title1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+      title1.IsDockedInsideChartArea = false;
+      title1.Name = "Title";
+      title1.Text = "[Title]";
+      this.chart.Titles.Add(title1);
+      this.chart.SelectionRangeChanged += new System.EventHandler<System.Windows.Forms.DataVisualization.Charting.CursorEventArgs>(this.chart_SelectionRangeChanged);
+      this.chart.PostPaint += new System.EventHandler<System.Windows.Forms.DataVisualization.Charting.ChartPaintEventArgs>(this.chart_PostPaint);
+      this.chart.DragDrop += new System.Windows.Forms.DragEventHandler(this.chart_DragDrop);
+      this.chart.DragEnter += new System.Windows.Forms.DragEventHandler(this.chart_DragEnter);
+      this.chart.MouseClick += new System.Windows.Forms.MouseEventHandler(this.chart_MouseClick);
+      this.chart.Resize += new System.EventHandler(this.chart_Resize);
+      // 
+      // FactorGradientChart
+      // 
+      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+      this.Controls.Add(this.calculationPendingLabel);
+      this.Controls.Add(this.chart);
+      this.Name = "FactorGradientChart";
+      this.Size = new System.Drawing.Size(453, 308);
+      ((System.ComponentModel.ISupportInitialize)(this.chart)).EndInit();
+      this.ResumeLayout(false);
+
+    }
+
+    #endregion
+
+    private HeuristicLab.Visualization.ChartControlsExtensions.EnhancedChart chart;
+    private System.Windows.Forms.Label calculationPendingLabel;
+    private System.Windows.Forms.Timer calculationPendingTimer;
+    private System.Windows.Forms.ToolTip toolTip;
+  }
+}
Index: trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/FactorGradientChart.cs
===================================================================
--- trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/FactorGradientChart.cs	(revision 14826)
+++ trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/FactorGradientChart.cs	(revision 14826)
@@ -0,0 +1,565 @@
+﻿#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;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.Windows.Forms.DataVisualization.Charting;
+using HeuristicLab.Common;
+using HeuristicLab.MainForm.WindowsForms;
+using HeuristicLab.Visualization.ChartControlsExtensions;
+
+namespace HeuristicLab.Problems.DataAnalysis.Views {
+  public partial class FactorGradientChart : UserControl, IGradientChart {
+    private ModifiableDataset sharedFixedVariables; // used for syncronising variable values between charts
+    private ModifiableDataset internalDataset; // holds the x values for each point drawn
+
+    private CancellationTokenSource cancelCurrentRecalculateSource;
+
+    private readonly List<IRegressionSolution> solutions;
+    private readonly Dictionary<IRegressionSolution, Series> seriesCache;
+    private readonly Dictionary<IRegressionSolution, Series> ciSeriesCache;
+
+    #region Properties
+    public string XAxisTitle {
+      get { return chart.ChartAreas[0].AxisX.Title; }
+      set { chart.ChartAreas[0].AxisX.Title = value; }
+    }
+
+    public string YAxisTitle {
+      get { return chart.ChartAreas[0].AxisY.Title; }
+      set { chart.ChartAreas[0].AxisY.Title = value; }
+    }
+
+    public bool ShowLegend {
+      get { return chart.Legends[0].Enabled; }
+      set { chart.Legends[0].Enabled = value; }
+    }
+    public bool ShowCursor {
+      get { return chart.Annotations[0].Visible; }
+      set {
+        chart.Annotations[0].Visible = value;
+        if (!value) chart.Titles[0].Text = string.Empty;
+      }
+    }
+
+    private int yAxisTicks = 5;
+    public int YAxisTicks {
+      get { return yAxisTicks; }
+      set {
+        if (value != yAxisTicks) {
+          yAxisTicks = value;
+          SetupAxis(chart.ChartAreas[0].AxisY, yMin, yMax, YAxisTicks, FixedYAxisMin, FixedYAxisMax);
+          RecalculateInternalDataset();
+        }
+      }
+    }
+    private double? fixedYAxisMin;
+    public double? FixedYAxisMin {
+      get { return fixedYAxisMin; }
+      set {
+        if ((value.HasValue && fixedYAxisMin.HasValue && !value.Value.IsAlmost(fixedYAxisMin.Value)) || (value.HasValue != fixedYAxisMin.HasValue)) {
+          fixedYAxisMin = value;
+          SetupAxis(chart.ChartAreas[0].AxisY, yMin, yMax, YAxisTicks, FixedYAxisMin, FixedYAxisMax);
+        }
+      }
+    }
+    private double? fixedYAxisMax;
+    public double? FixedYAxisMax {
+      get { return fixedYAxisMax; }
+      set {
+        if ((value.HasValue && fixedYAxisMax.HasValue && !value.Value.IsAlmost(fixedYAxisMax.Value)) || (value.HasValue != fixedYAxisMax.HasValue)) {
+          fixedYAxisMax = value;
+          SetupAxis(chart.ChartAreas[0].AxisY, yMin, yMax, YAxisTicks, FixedYAxisMin, FixedYAxisMax);
+        }
+      }
+    }
+
+    private string freeVariable;
+    public string FreeVariable {
+      get { return freeVariable; }
+      set {
+        if (value == freeVariable) return;
+        if (solutions.Any(s => !s.ProblemData.Dataset.StringVariables.Contains(value))) {
+          throw new ArgumentException("Variable does not exist in the ProblemData of the Solutions.");
+        }
+        freeVariable = value;
+        RecalculateInternalDataset();
+      }
+    }
+
+    private double yMin;
+    public double YMin {
+      get { return yMin; }
+    }
+    private double yMax;
+    public double YMax {
+      get { return yMax; }
+    }
+
+    public bool IsZoomed {
+      get { return chart.ChartAreas[0].AxisX.ScaleView.IsZoomed; }
+    }
+
+    internal ElementPosition InnerPlotPosition {
+      get { return chart.ChartAreas[0].InnerPlotPosition; }
+    }
+    #endregion
+
+    private List<string> variableValues;
+
+    public event EventHandler ChartPostPaint;
+
+    public FactorGradientChart() {
+      InitializeComponent();
+
+      solutions = new List<IRegressionSolution>();
+      seriesCache = new Dictionary<IRegressionSolution, Series>();
+      ciSeriesCache = new Dictionary<IRegressionSolution, Series>();
+
+      // Configure axis
+      chart.CustomizeAllChartAreas();
+      chart.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
+      chart.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
+      chart.ChartAreas[0].CursorX.Interval = 0;
+
+      chart.ChartAreas[0].CursorY.IsUserSelectionEnabled = true;
+      chart.ChartAreas[0].AxisY.ScaleView.Zoomable = true;
+      chart.ChartAreas[0].CursorY.Interval = 0;
+
+      Disposed += GradientChart_Disposed;
+    }
+
+    private void GradientChart_Disposed(object sender, EventArgs e) {
+      if (cancelCurrentRecalculateSource != null)
+        cancelCurrentRecalculateSource.Cancel();
+    }
+
+    public void Configure(IEnumerable<IRegressionSolution> solutions, ModifiableDataset sharedFixedVariables, string freeVariable, IList<string> variableValues, bool initializeAxisRanges = true) {
+      if (!SolutionsCompatible(solutions))
+        throw new ArgumentException("Solutions are not compatible with the problem data.");
+      this.freeVariable = freeVariable;
+      this.variableValues = new List<string>(variableValues);
+
+      this.solutions.Clear();
+      this.solutions.AddRange(solutions);
+
+      // add an event such that whenever a value is changed in the shared dataset, 
+      // this change is reflected in the internal dataset (where the value becomes a whole column)
+      if (this.sharedFixedVariables != null)
+        this.sharedFixedVariables.ItemChanged -= sharedFixedVariables_ItemChanged;
+      this.sharedFixedVariables = sharedFixedVariables;
+      this.sharedFixedVariables.ItemChanged += sharedFixedVariables_ItemChanged;
+
+      RecalculateInternalDataset();
+
+      chart.Series.Clear();
+      seriesCache.Clear();
+      ciSeriesCache.Clear();
+      foreach (var solution in this.solutions) {
+        var series = CreateSeries(solution);
+        seriesCache.Add(solution, series.Item1);
+        if (series.Item2 != null)
+          ciSeriesCache.Add(solution, series.Item2);
+      }
+
+      InitSeriesData();
+      OrderAndColorSeries();
+
+    }
+
+    public async Task RecalculateAsync(bool updateOnFinish = true, bool resetYAxis = true) {
+      if (IsDisposed
+        || sharedFixedVariables == null || !solutions.Any() || string.IsNullOrEmpty(freeVariable)
+        || !variableValues.Any())
+        return;
+
+      calculationPendingTimer.Start();
+
+      // cancel previous recalculate call
+      if (cancelCurrentRecalculateSource != null)
+        cancelCurrentRecalculateSource.Cancel();
+      cancelCurrentRecalculateSource = new CancellationTokenSource();
+      var cancellationToken = cancelCurrentRecalculateSource.Token;
+
+      // Update series
+      try {
+        var limits = await UpdateAllSeriesDataAsync(cancellationToken);
+
+        yMin = limits.Lower;
+        yMax = limits.Upper;
+        // Set y-axis
+        if (resetYAxis)
+          SetupAxis(chart.ChartAreas[0].AxisY, yMin, yMax, YAxisTicks, FixedYAxisMin, FixedYAxisMax);
+
+        calculationPendingTimer.Stop();
+        calculationPendingLabel.Visible = false;
+        if (updateOnFinish)
+          Update();
+      } catch (OperationCanceledException) { } catch (AggregateException ae) {
+        if (!ae.InnerExceptions.Any(e => e is OperationCanceledException))
+          throw;
+      }
+    }
+
+    public void UpdateTitlePosition() {
+      var title = chart.Titles[0];
+      var plotArea = InnerPlotPosition;
+
+      title.Visible = plotArea.Width != 0;
+
+      title.Position.X = plotArea.X + (plotArea.Width / 2);
+    }
+
+    private void SetupAxis(Axis axis, double minValue, double maxValue, int ticks, double? fixedAxisMin, double? fixedAxisMax) {
+      if (minValue < maxValue) {
+        double axisMin, axisMax, axisInterval;
+        ChartUtil.CalculateAxisInterval(minValue, maxValue, ticks, out axisMin, out axisMax, out axisInterval);
+        axis.Minimum = fixedAxisMin ?? axisMin;
+        axis.Maximum = fixedAxisMax ?? axisMax;
+        axis.Interval = (axis.Maximum - axis.Minimum) / ticks;
+      }
+
+      try {
+        chart.ChartAreas[0].RecalculateAxesScale();
+      } catch (InvalidOperationException) {
+        // Can occur if eg. axis min == axis max
+      }
+    }
+
+
+    private void RecalculateInternalDataset() {
+      if (sharedFixedVariables == null)
+        return;
+
+      var factorValues = new List<string>(variableValues);
+
+      var variables = sharedFixedVariables.VariableNames.ToList();
+      var values = new List<IList>();
+      foreach (var varName in variables) {
+        if (varName == FreeVariable) {
+          values.Add(factorValues);
+        } else if (sharedFixedVariables.VariableHasType<double>(varName)) {
+          values.Add(Enumerable.Repeat(sharedFixedVariables.GetDoubleValue(varName, 0), factorValues.Count).ToList());
+        } else if (sharedFixedVariables.VariableHasType<string>(varName)) {
+          values.Add(Enumerable.Repeat(sharedFixedVariables.GetStringValue(varName, 0), factorValues.Count).ToList());
+        }
+      }
+
+      internalDataset = new ModifiableDataset(variables, values);
+    }
+
+    private Tuple<Series, Series> CreateSeries(IRegressionSolution solution) {
+      var series = new Series {
+        ChartType = SeriesChartType.Column,
+        Name = solution.ProblemData.TargetVariable + " " + solutions.IndexOf(solution),
+        XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.String
+      };
+      series.LegendText = series.Name;
+
+      Series confidenceIntervalSeries = null;
+      confidenceIntervalSeries = new Series {
+        ChartType = SeriesChartType.BoxPlot,
+        XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.String,
+        Color = Color.Black,
+        YValuesPerPoint = 5,
+        Name = "95% Conf. Interval " + series.Name,
+        IsVisibleInLegend = false
+      };
+      return Tuple.Create(series, confidenceIntervalSeries);
+    }
+
+    private void OrderAndColorSeries() {
+      chart.SuspendRepaint();
+
+      chart.Series.Clear();
+      // Add mean series for applying palette colors
+      foreach (var solution in solutions) {
+        chart.Series.Add(seriesCache[solution]);
+      }
+
+      chart.Palette = ChartColorPalette.BrightPastel;
+      chart.ApplyPaletteColors();
+      chart.Palette = ChartColorPalette.None;
+
+      // Add confidence interval series after its coresponding series for correct z index
+      foreach (var solution in solutions) {
+        Series ciSeries;
+        if (ciSeriesCache.TryGetValue(solution, out ciSeries)) {
+          int idx = chart.Series.IndexOf(seriesCache[solution]);
+          chart.Series.Insert(idx + 1, ciSeries);
+        }
+      }
+
+      chart.ResumeRepaint(true);
+    }
+
+    private async Task<DoubleLimit> UpdateAllSeriesDataAsync(CancellationToken cancellationToken) {
+      var updateTasks = solutions.Select(solution => UpdateSeriesDataAsync(solution, cancellationToken));
+
+      double min = double.MaxValue, max = double.MinValue;
+      foreach (var update in updateTasks) {
+        var limit = await update;
+        if (limit.Lower < min) min = limit.Lower;
+        if (limit.Upper > max) max = limit.Upper;
+      }
+
+      return new DoubleLimit(min, max);
+    }
+
+    private Task<DoubleLimit> UpdateSeriesDataAsync(IRegressionSolution solution, CancellationToken cancellationToken) {
+      return Task.Run(() => {
+        var yvalues = solution.Model.GetEstimatedValues(internalDataset, Enumerable.Range(0, internalDataset.Rows)).ToList();
+
+        double min = double.MaxValue, max = double.MinValue;
+
+        var series = seriesCache[solution];
+        for (int i = 0; i < variableValues.Count; i++) {
+          series.Points[i].SetValueXY(variableValues[i], yvalues[i]);
+          if (yvalues[i] < min) min = yvalues[i];
+          if (yvalues[i] > max) max = yvalues[i];
+        }
+        chart.Invalidate();
+
+        cancellationToken.ThrowIfCancellationRequested();
+
+        var confidenceBoundSolution = solution as IConfidenceRegressionSolution;
+        if (confidenceBoundSolution != null) {
+          var confidenceIntervalSeries = ciSeriesCache[solution];
+          var variances = confidenceBoundSolution.Model.GetEstimatedVariances(internalDataset, Enumerable.Range(0, internalDataset.Rows)).ToList();
+          for (int i = 0; i < variableValues.Count; i++) {
+            var lower = yvalues[i] - 1.96 * Math.Sqrt(variances[i]);
+            var upper = yvalues[i] + 1.96 * Math.Sqrt(variances[i]);
+            confidenceIntervalSeries.Points[i].SetValueXY(variableValues[i], lower, upper, yvalues[i], yvalues[i], yvalues[i]);
+            if (lower < min) min = lower;
+            if (upper > max) max = upper;
+          }
+          chart.Invalidate();
+        }
+
+        cancellationToken.ThrowIfCancellationRequested();
+        return new DoubleLimit(min, max);
+      }, cancellationToken);
+    }
+
+    private void InitSeriesData() {
+      if (internalDataset == null)
+        return;
+
+      foreach (var solution in solutions)
+        InitSeriesData(solution, variableValues);
+    }
+
+    private void InitSeriesData(IRegressionSolution solution, IList<string> values) {
+
+      var series = seriesCache[solution];
+      series.Points.SuspendUpdates();
+      series.Points.Clear();
+      for (int i = 0; i < values.Count; i++) {
+        series.Points.AddXY(values[i], 0.0);
+        series.Points.Last().ToolTip = values[i];
+      }
+
+      UpdateAllSeriesStyles(variableValues.IndexOf(sharedFixedVariables.GetStringValue(FreeVariable, 0)));
+      series.Points.ResumeUpdates();
+
+      Series confidenceIntervalSeries;
+      if (ciSeriesCache.TryGetValue(solution, out confidenceIntervalSeries)) {
+        confidenceIntervalSeries.Points.SuspendUpdates();
+        confidenceIntervalSeries.Points.Clear();
+        for (int i = 0; i < values.Count; i++)
+          confidenceIntervalSeries.Points.AddXY(values[i], 0.0, 0.0, 0.0, 0.0, 0.0);
+        confidenceIntervalSeries.Points.ResumeUpdates();
+      }
+    }
+
+    public async Task AddSolutionAsync(IRegressionSolution solution) {
+      if (!SolutionsCompatible(solutions.Concat(new[] { solution })))
+        throw new ArgumentException("The solution is not compatible with the problem data.");
+      if (solutions.Contains(solution))
+        return;
+
+      solutions.Add(solution);
+
+      var series = CreateSeries(solution);
+      seriesCache.Add(solution, series.Item1);
+      if (series.Item2 != null)
+        ciSeriesCache.Add(solution, series.Item2);
+
+      InitSeriesData(solution, variableValues);
+      OrderAndColorSeries();
+
+      await RecalculateAsync();
+      var args = new EventArgs<IRegressionSolution>(solution);
+      OnSolutionAdded(this, args);
+    }
+
+    public async Task RemoveSolutionAsync(IRegressionSolution solution) {
+      throw new NotSupportedException();
+    }
+
+    private static bool SolutionsCompatible(IEnumerable<IRegressionSolution> solutions) {
+      var refSolution = solutions.First();
+      var refSolVars = refSolution.ProblemData.Dataset.VariableNames;
+      foreach (var solution in solutions.Skip(1)) {
+        var variables1 = solution.ProblemData.Dataset.VariableNames;
+        if (!variables1.All(refSolVars.Contains))
+          return false;
+
+        foreach (var factorVar in variables1.Where(solution.ProblemData.Dataset.VariableHasType<string>)) {
+          var distinctVals = refSolution.ProblemData.Dataset.GetStringValues(factorVar).Distinct();
+          if (solution.ProblemData.Dataset.GetStringValues(factorVar).Any(val => !distinctVals.Contains(val))) return false;
+        }
+      }
+      return true;
+    }
+
+    #region Events
+    public event EventHandler<EventArgs<IRegressionSolution>> SolutionAdded;
+    public void OnSolutionAdded(object sender, EventArgs<IRegressionSolution> args) {
+      var added = SolutionAdded;
+      if (added == null) return;
+      added(sender, args);
+    }
+
+    public event EventHandler<EventArgs<IRegressionSolution>> SolutionRemoved;
+    public void OnSolutionRemoved(object sender, EventArgs<IRegressionSolution> args) {
+      var removed = SolutionRemoved;
+      if (removed == null) return;
+      removed(sender, args);
+    }
+
+    public event EventHandler VariableValueChanged;
+    public void OnVariableValueChanged(object sender, EventArgs args) {
+      var changed = VariableValueChanged;
+      if (changed == null) return;
+      changed(sender, args);
+    }
+
+    public event EventHandler ZoomChanged;
+    public void OnZoomChanged(object sender, EventArgs args) {
+      var changed = ZoomChanged;
+      if (changed == null) return;
+      changed(sender, args);
+    }
+
+    private void sharedFixedVariables_ItemChanged(object o, EventArgs<int, int> e) {
+      if (o != sharedFixedVariables) return;
+      var variables = sharedFixedVariables.VariableNames.ToList();
+      var rowIndex = e.Value;
+      var columnIndex = e.Value2;
+
+      var variableName = variables[columnIndex];
+      if (variableName == FreeVariable) return;
+      if (internalDataset.VariableHasType<double>(variableName)) {
+        var v = sharedFixedVariables.GetDoubleValue(variableName, rowIndex);
+        var values = new List<double>(Enumerable.Repeat(v, internalDataset.Rows));
+        internalDataset.ReplaceVariable(variableName, values);
+      } else if (internalDataset.VariableHasType<string>(variableName)) {
+        var v = sharedFixedVariables.GetStringValue(variableName, rowIndex);
+        var values = new List<String>(Enumerable.Repeat(v, internalDataset.Rows));
+        internalDataset.ReplaceVariable(variableName, values);
+      } else {
+        // unsupported type 
+        throw new NotSupportedException();
+      }
+    }
+
+    private async void chart_DragDrop(object sender, DragEventArgs e) {
+      var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat);
+      if (data != null) {
+        var solution = data as IRegressionSolution;
+        if (!solutions.Contains(solution))
+          await AddSolutionAsync(solution);
+      }
+    }
+    private void chart_DragEnter(object sender, DragEventArgs e) {
+      if (!e.Data.GetDataPresent(HeuristicLab.Common.Constants.DragDropDataFormat)) return;
+      e.Effect = DragDropEffects.None;
+
+      var data = e.Data.GetData(HeuristicLab.Common.Constants.DragDropDataFormat);
+      var regressionSolution = data as IRegressionSolution;
+      if (regressionSolution != null) {
+        e.Effect = DragDropEffects.Copy;
+      }
+    }
+
+    private void calculationPendingTimer_Tick(object sender, EventArgs e) {
+      calculationPendingLabel.Visible = true;
+      Update();
+    }
+
+    private void chart_SelectionRangeChanged(object sender, CursorEventArgs e) {
+      OnZoomChanged(this, EventArgs.Empty);
+    }
+
+    private void chart_Resize(object sender, EventArgs e) {
+      UpdateTitlePosition();
+    }
+
+    private void chart_PostPaint(object sender, ChartPaintEventArgs e) {
+      if (ChartPostPaint != null)
+        ChartPostPaint(this, EventArgs.Empty);
+    }
+    #endregion
+
+    private void chart_MouseClick(object sender, MouseEventArgs e) {
+      var hitTestResult = chart.HitTest(e.X, e.Y, ChartElementType.DataPoint);
+      if (hitTestResult != null && hitTestResult.ChartElementType == ChartElementType.DataPoint) {
+        var series = hitTestResult.Series;
+        var dataPoint = (DataPoint)hitTestResult.Object;
+        var idx = series.Points.IndexOf(dataPoint);
+        UpdateSelectedValue(variableValues[idx]);
+
+        UpdateAllSeriesStyles(idx);
+      }
+    }
+
+    private void UpdateAllSeriesStyles(int selectedValueIndex) {
+      if (ShowCursor) {
+        chart.Titles[0].Text = FreeVariable + " : " + variableValues[selectedValueIndex];
+        chart.Update();
+      }
+      foreach (var s in seriesCache.Values) {
+        if (s.ChartType == SeriesChartType.Column)
+          for (int i = 0; i < s.Points.Count; i++) {
+            if (i != selectedValueIndex) {
+              s.Points[i].BorderDashStyle = ChartDashStyle.NotSet;
+            } else {
+              s.Points[i].BorderDashStyle = ChartDashStyle.Dash;
+              s.Points[i].BorderColor = Color.Red;
+            }
+          }
+      }
+    }
+
+    private void UpdateSelectedValue(string variableValue) {
+      sharedFixedVariables.SetVariableValue(variableValue, FreeVariable, 0);
+      OnVariableValueChanged(this, EventArgs.Empty);
+    }
+  }
+}
+
Index: trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/GradientChart.Designer.cs
===================================================================
--- trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/GradientChart.Designer.cs	(revision 14267)
+++ trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/GradientChart.Designer.cs	(revision 14826)
@@ -25,5 +25,4 @@
     private void InitializeComponent() {
       this.components = new System.ComponentModel.Container();
-      System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(GradientChart));
       System.Windows.Forms.DataVisualization.Charting.VerticalLineAnnotation verticalLineAnnotation1 = new System.Windows.Forms.DataVisualization.Charting.VerticalLineAnnotation();
       System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
Index: trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/GradientChart.cs
===================================================================
--- trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/GradientChart.cs	(revision 14267)
+++ trunk/sources/HeuristicLab.Problems.DataAnalysis.Views/3.4/Controls/GradientChart.cs	(revision 14826)
@@ -21,4 +21,5 @@
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Drawing;
@@ -34,5 +35,5 @@
 
 namespace HeuristicLab.Problems.DataAnalysis.Views {
-  public partial class GradientChart : UserControl {
+  public partial class GradientChart : UserControl, IGradientChart {
     private ModifiableDataset sharedFixedVariables; // used for syncronising variable values between charts
     private ModifiableDataset internalDataset; // holds the x values for each point drawn
@@ -319,7 +320,5 @@
         if (updateOnFinish)
           Update();
-      }
-      catch (OperationCanceledException) { }
-      catch (AggregateException ae) {
+      } catch (OperationCanceledException) { } catch (AggregateException ae) {
         if (!ae.InnerExceptions.Any(e => e is OperationCanceledException))
           throw;
@@ -347,6 +346,5 @@
       try {
         chart.ChartAreas[0].RecalculateAxesScale();
-      }
-      catch (InvalidOperationException) {
+      } catch (InvalidOperationException) {
         // Can occur if eg. axis min == axis max
       }
@@ -381,11 +379,20 @@
         xvalues.Add(xmin + i * step);
 
-      var variables = sharedFixedVariables.DoubleVariables.ToList();
-      internalDataset = new ModifiableDataset(variables,
-        variables.Select(x => x == FreeVariable
-          ? xvalues
-          : Enumerable.Repeat(sharedFixedVariables.GetDoubleValue(x, 0), xvalues.Count).ToList()
-        )
-      );
+      if (sharedFixedVariables == null)
+        return;
+
+      var variables = sharedFixedVariables.VariableNames.ToList();
+      var values = new List<IList>();
+      foreach (var varName in variables) {
+        if (varName == FreeVariable) {
+          values.Add(xvalues);
+        } else if (sharedFixedVariables.VariableHasType<double>(varName)) {
+          values.Add(Enumerable.Repeat(sharedFixedVariables.GetDoubleValue(varName, 0), xvalues.Count).ToList());
+        } else if (sharedFixedVariables.VariableHasType<string>(varName)) {
+          values.Add(Enumerable.Repeat(sharedFixedVariables.GetStringValue(varName, 0), xvalues.Count).ToList());
+        }
+      }
+
+      internalDataset = new ModifiableDataset(variables, values);
     }
 
@@ -552,12 +559,14 @@
 
     private static bool SolutionsCompatible(IEnumerable<IRegressionSolution> solutions) {
-      foreach (var solution1 in solutions) {
-        var variables1 = solution1.ProblemData.Dataset.DoubleVariables;
-        foreach (var solution2 in solutions) {
-          if (solution1 == solution2)
-            continue;
-          var variables2 = solution2.ProblemData.Dataset.DoubleVariables;
-          if (!variables1.All(variables2.Contains))
-            return false;
+      var refSolution = solutions.First();
+      var refSolVars = refSolution.ProblemData.Dataset.VariableNames;
+      foreach (var solution in solutions.Skip(1)) {
+        var variables1 = solution.ProblemData.Dataset.VariableNames;
+        if (!variables1.All(refSolVars.Contains))
+          return false;
+
+        foreach (var factorVar in variables1.Where(solution.ProblemData.Dataset.VariableHasType<string>)) {
+          var distinctVals = refSolution.ProblemData.Dataset.GetStringValues(factorVar).Distinct();
+          if (solution.ProblemData.Dataset.GetStringValues(factorVar).Any(val => !distinctVals.Contains(val))) return false;
         }
       }
@@ -608,5 +617,5 @@
     private void sharedFixedVariables_ItemChanged(object o, EventArgs<int, int> e) {
       if (o != sharedFixedVariables) return;
-      var variables = sharedFixedVariables.DoubleVariables.ToList();
+      var variables = sharedFixedVariables.VariableNames.ToList();
       var rowIndex = e.Value;
       var columnIndex = e.Value2;
@@ -614,7 +623,16 @@
       var variableName = variables[columnIndex];
       if (variableName == FreeVariable) return;
-      var v = sharedFixedVariables.GetDoubleValue(variableName, rowIndex);
-      var values = new List<double>(Enumerable.Repeat(v, DrawingSteps));
-      internalDataset.ReplaceVariable(variableName, values);
+      if (internalDataset.VariableHasType<double>(variableName)) {
+        var v = sharedFixedVariables.GetDoubleValue(variableName, rowIndex);
+        var values = new List<double>(Enumerable.Repeat(v, internalDataset.Rows));
+        internalDataset.ReplaceVariable(variableName, values);
+      } else if (internalDataset.VariableHasType<string>(variableName)) {
+        var v = sharedFixedVariables.GetStringValue(variableName, rowIndex);
+        var values = new List<String>(Enumerable.Repeat(v, internalDataset.Rows));
+        internalDataset.ReplaceVariable(variableName, values);
+      } else {
+        // unsupported type 
+        throw new NotSupportedException();
+      }
     }
 
@@ -635,5 +653,5 @@
       UpdateCursor();
     }
-    void UpdateCursor() {
+    private void UpdateCursor() {
       var x = VerticalLineAnnotation.X;
       sharedFixedVariables.SetVariableValue(x, FreeVariable, 0);
