Index: /trunk/sources/HeuristicLab.Analysis.Views/3.3/DataTableVisualPropertiesControl.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis.Views/3.3/DataTableVisualPropertiesControl.cs	(revision 8279)
+++ /trunk/sources/HeuristicLab.Analysis.Views/3.3/DataTableVisualPropertiesControl.cs	(revision 8280)
@@ -26,5 +26,5 @@
 
 namespace HeuristicLab.Analysis.Views {
-  [View("DataRow Visual Properties")]
+  [View("DataTable Visual Properties")]
   public partial class DataTableVisualPropertiesControl : UserControl {
     protected bool SuppressEvents { get; set; }
Index: /trunk/sources/HeuristicLab.Analysis.Views/3.3/HeuristicLab.Analysis.Views-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Analysis.Views/3.3/HeuristicLab.Analysis.Views-3.3.csproj	(revision 8279)
+++ /trunk/sources/HeuristicLab.Analysis.Views/3.3/HeuristicLab.Analysis.Views-3.3.csproj	(revision 8280)
@@ -122,4 +122,10 @@
       <DependentUpon>ScatterPlotView.cs</DependentUpon>
     </Compile>
+    <Compile Include="ScatterPlotHistoryView.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="ScatterPlotHistoryView.Designer.cs">
+      <DependentUpon>ScatterPlotHistoryView.cs</DependentUpon>
+    </Compile>
     <None Include="Plugin.cs.frame" />
     <Compile Include="AlleleFrequencyCollectionView.cs">
Index: /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotHistoryView.Designer.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotHistoryView.Designer.cs	(revision 8280)
+++ /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotHistoryView.Designer.cs	(revision 8280)
@@ -0,0 +1,53 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+namespace HeuristicLab.Analysis.Views {
+  partial class ScatterPlotHistoryView {
+    /// <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) {
+        if (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() {
+      components = new System.ComponentModel.Container();
+      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit;
+    }
+
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotHistoryView.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotHistoryView.cs	(revision 8280)
+++ /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotHistoryView.cs	(revision 8280)
@@ -0,0 +1,35 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2012 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.Windows.Forms;
+using HeuristicLab.Core.Views;
+using HeuristicLab.MainForm;
+
+namespace HeuristicLab.Analysis.Views {
+  [View("ScatterPlotHistory View")]
+  [Content(typeof(ScatterPlotHistory), true)]
+  public partial class ScatterPlotHistoryView : MovieView<ScatterPlot> {
+    public ScatterPlotHistoryView() {
+      InitializeComponent();
+      itemsGroupBox.Text = "Scatter Plots";
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotView.Designer.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotView.Designer.cs	(revision 8279)
+++ /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotView.Designer.cs	(revision 8280)
@@ -1,3 +1,3 @@
-﻿#region License Information
+#region License Information
 /* HeuristicLab
  * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
@@ -22,28 +22,29 @@
 namespace HeuristicLab.Analysis.Views {
   partial class ScatterPlotView {
-    /// <summary>
+    /// <summary> 
     /// Required designer variable.
     /// </summary>
     private System.ComponentModel.IContainer components = null;
 
-    /// <summary>
+    /// <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();
+      if (disposing) {
+        if (components != null) components.Dispose();
       }
       base.Dispose(disposing);
     }
 
-    #region Windows Form Designer generated code
+    #region Component Designer generated code
 
-    /// <summary>
-    /// Required method for Designer support - do not modify
+    /// <summary> 
+    /// Required method for Designer support - do not modify 
     /// the contents of this method with the code editor.
     /// </summary>
     private void InitializeComponent() {
       System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
+      System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend();
       System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
       System.Windows.Forms.DataVisualization.Charting.Title title1 = new System.Windows.Forms.DataVisualization.Charting.Title();
@@ -57,36 +58,53 @@
       this.errorProvider.SetIconAlignment(this.nameTextBox, System.Windows.Forms.ErrorIconAlignment.MiddleLeft);
       this.errorProvider.SetIconPadding(this.nameTextBox, 2);
-      this.nameTextBox.Size = new System.Drawing.Size(596, 20);
+      this.nameTextBox.Location = new System.Drawing.Point(55, 0);
+      this.nameTextBox.Size = new System.Drawing.Size(279, 20);
+      // 
+      // infoLabel
+      // 
+      this.infoLabel.Location = new System.Drawing.Point(340, 3);
       // 
       // chart
       // 
-      this.chart.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
-            | System.Windows.Forms.AnchorStyles.Left) 
-            | System.Windows.Forms.AnchorStyles.Right)));
-      chartArea1.Name = "ChartArea1";
+      this.chart.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
+                  | System.Windows.Forms.AnchorStyles.Left)
+                  | System.Windows.Forms.AnchorStyles.Right)));
+      this.chart.BorderlineColor = System.Drawing.Color.Black;
+      this.chart.BorderlineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Solid;
+      chartArea1.AxisX.Minimum = 0D;
+      chartArea1.CursorX.IsUserEnabled = true;
+      chartArea1.CursorX.IsUserSelectionEnabled = true;
+      chartArea1.CursorY.IsUserEnabled = true;
+      chartArea1.CursorY.IsUserSelectionEnabled = true;
+      chartArea1.Name = "Default";
       this.chart.ChartAreas.Add(chartArea1);
+      legend1.Alignment = System.Drawing.StringAlignment.Center;
+      legend1.Docking = System.Windows.Forms.DataVisualization.Charting.Docking.Top;
+      legend1.Name = "Default";
+      this.chart.Legends.Add(legend1);
       this.chart.Location = new System.Drawing.Point(0, 26);
       this.chart.Name = "chart";
-      series1.ChartArea = "ChartArea1";
-      series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.FastPoint;
-      series1.EmptyPointStyle.BackGradientStyle = System.Windows.Forms.DataVisualization.Charting.GradientStyle.LeftRight;
-      series1.MarkerSize = 3;
-      series1.MarkerStyle = System.Windows.Forms.DataVisualization.Charting.MarkerStyle.Circle;
-      series1.Name = "Series1";
+      series1.ChartArea = "Default";
+      series1.Legend = "Default";
+      series1.Name = "Default";
       this.chart.Series.Add(series1);
-      this.chart.Size = new System.Drawing.Size(668, 378);
-      this.chart.TabIndex = 5;
-      title1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-      title1.Name = "Title1";
-      title1.Text = "Scatter Plot";
+      this.chart.Size = new System.Drawing.Size(359, 248);
+      this.chart.TabIndex = 3;
+      this.chart.Text = "chart";
+      title1.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+      title1.Name = "Default";
+      title1.Text = "Title";
       this.chart.Titles.Add(title1);
+      this.chart.CustomizeLegend += new System.EventHandler<System.Windows.Forms.DataVisualization.Charting.CustomizeLegendEventArgs>(this.chart_CustomizeLegend);
+      this.chart.MouseDown += new System.Windows.Forms.MouseEventHandler(this.chart_MouseDown);
+      this.chart.MouseMove += new System.Windows.Forms.MouseEventHandler(this.chart_MouseMove);
       // 
       // ScatterPlotView
       // 
-      this.AllowDrop = true;
       this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit;
       this.Controls.Add(this.chart);
       this.Name = "ScatterPlotView";
-      this.Size = new System.Drawing.Size(668, 404);
+      this.Size = new System.Drawing.Size(359, 274);
       this.Controls.SetChildIndex(this.infoLabel, 0);
       this.Controls.SetChildIndex(this.chart, 0);
@@ -102,5 +120,6 @@
     #endregion
 
-    private HeuristicLab.Visualization.ChartControlsExtensions.EnhancedChart chart;
+    protected HeuristicLab.Visualization.ChartControlsExtensions.EnhancedChart chart;
+
   }
 }
Index: /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotView.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotView.cs	(revision 8279)
+++ /trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotView.cs	(revision 8280)
@@ -1,3 +1,3 @@
-﻿#region License Information
+#region License Information
 /* HeuristicLab
  * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
@@ -21,7 +21,11 @@
 
 using System;
+using System.Collections.Generic;
 using System.Drawing;
+using System.Linq;
 using System.Windows.Forms;
+using System.Windows.Forms.DataVisualization.Charting;
 using HeuristicLab.Collections;
+using HeuristicLab.Common;
 using HeuristicLab.Core.Views;
 using HeuristicLab.MainForm;
@@ -30,5 +34,8 @@
   [View("ScatterPlot View")]
   [Content(typeof(ScatterPlot), true)]
-  public partial class ScatterPlotView : NamedItemView {
+  public partial class ScatterPlotView : NamedItemView, IConfigureableView {
+    protected List<Series> invisibleSeries;
+    protected Dictionary<IObservableList<Point2D<double>>, ScatterPlotDataRow> pointsRowsTable;
+
     public new ScatterPlot Content {
       get { return (ScatterPlot)base.Content; }
@@ -38,76 +45,445 @@
     public ScatterPlotView() {
       InitializeComponent();
+      pointsRowsTable = new Dictionary<IObservableList<Point2D<double>>, ScatterPlotDataRow>();
+      invisibleSeries = new List<Series>();
       chart.CustomizeAllChartAreas();
-    }
-
+      chart.ChartAreas[0].CursorX.Interval = 1;
+    }
+
+    #region Event Handler Registration
     protected override void DeregisterContentEvents() {
-      Content.Points.ItemsAdded -= new CollectionItemsChangedEventHandler<IndexedItem<PointF>>(Content_Points_Changed);
-      Content.Points.ItemsRemoved -= new CollectionItemsChangedEventHandler<IndexedItem<PointF>>(Content_Points_Changed);
-      Content.Points.ItemsReplaced -= new CollectionItemsChangedEventHandler<IndexedItem<PointF>>(Content_Points_Changed);
-      Content.Points.CollectionReset -= new CollectionItemsChangedEventHandler<IndexedItem<PointF>>(Content_Points_Changed);
-      Content.AxisNameChanged -= new EventHandler(Content_AxisNameChanged);
+      foreach (ScatterPlotDataRow row in Content.Rows)
+        DeregisterScatterPlotDataRowEvents(row);
+      Content.VisualPropertiesChanged -= new EventHandler(Content_VisualPropertiesChanged);
+      Content.Rows.ItemsAdded -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsAdded);
+      Content.Rows.ItemsRemoved -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsRemoved);
+      Content.Rows.ItemsReplaced -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsReplaced);
+      Content.Rows.CollectionReset -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_CollectionReset);
       base.DeregisterContentEvents();
     }
-
     protected override void RegisterContentEvents() {
       base.RegisterContentEvents();
-      Content.Points.ItemsAdded += new CollectionItemsChangedEventHandler<IndexedItem<PointF>>(Content_Points_Changed);
-      Content.Points.ItemsRemoved += new CollectionItemsChangedEventHandler<IndexedItem<PointF>>(Content_Points_Changed);
-      Content.Points.ItemsReplaced += new CollectionItemsChangedEventHandler<IndexedItem<PointF>>(Content_Points_Changed);
-      Content.Points.CollectionReset += new CollectionItemsChangedEventHandler<IndexedItem<PointF>>(Content_Points_Changed);
-      Content.AxisNameChanged += new EventHandler(Content_AxisNameChanged);
-    }
-
-    private void Content_Points_Changed(object sender, CollectionItemsChangedEventArgs<IndexedItem<PointF>> e) {
-      RedrawChart();
-    }
+      Content.VisualPropertiesChanged += new EventHandler(Content_VisualPropertiesChanged);
+      Content.Rows.ItemsAdded += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsAdded);
+      Content.Rows.ItemsRemoved += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsRemoved);
+      Content.Rows.ItemsReplaced += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsReplaced);
+      Content.Rows.CollectionReset += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_CollectionReset);
+    }
+
+    protected virtual void RegisterScatterPlotDataRowEvents(ScatterPlotDataRow row) {
+      row.NameChanged += new EventHandler(Row_NameChanged);
+      row.VisualPropertiesChanged += new EventHandler(Row_VisualPropertiesChanged);
+      pointsRowsTable.Add(row.Points, row);
+      row.Points.ItemsAdded += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded);
+      row.Points.ItemsRemoved += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved);
+      row.Points.ItemsReplaced += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced);
+      row.Points.CollectionReset += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset);
+    }
+    protected virtual void DeregisterScatterPlotDataRowEvents(ScatterPlotDataRow row) {
+      row.Points.ItemsAdded -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded);
+      row.Points.ItemsRemoved -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved);
+      row.Points.ItemsReplaced -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced);
+      row.Points.CollectionReset -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset);
+      pointsRowsTable.Remove(row.Points);
+      row.VisualPropertiesChanged -= new EventHandler(Row_VisualPropertiesChanged);
+      row.NameChanged -= new EventHandler(Row_NameChanged);
+    }
+    #endregion
 
     protected override void OnContentChanged() {
       base.OnContentChanged();
+      invisibleSeries.Clear();
+      chart.Titles[0].Text = string.Empty;
+      chart.ChartAreas[0].AxisX.Title = string.Empty;
+      chart.ChartAreas[0].AxisY.Title = string.Empty;
+      chart.Series.Clear();
       if (Content != null) {
-        ConfigureChart();
-        RedrawChart();
+        chart.Titles[0].Text = Content.Name;
+        AddScatterPlotDataRows(Content.Rows);
+        ConfigureChartArea(chart.ChartAreas[0]);
+        RecalculateAxesScale(chart.ChartAreas[0]);
+      }
+    }
+
+    protected override void SetEnabledStateOfControls() {
+      base.SetEnabledStateOfControls();
+      chart.Enabled = Content != null;
+    }
+
+    public void ShowConfiguration() {
+      //if (Content != null) {
+      //  using (ScatterPlotVisualPropertiesDialog dialog = new ScatterPlotVisualPropertiesDialog(Content)) {
+      //    dialog.ShowDialog(this);
+      //  }
+      //} else MessageBox.Show("Nothing to configure.");
+    }
+    protected virtual void AddScatterPlotDataRows(IEnumerable<ScatterPlotDataRow> rows) {
+      foreach (var row in rows) {
+        RegisterScatterPlotDataRowEvents(row);
+        Series series = new Series(row.Name);
+        if (row.VisualProperties.DisplayName.Trim() != String.Empty) series.LegendText = row.VisualProperties.DisplayName;
+        else series.LegendText = row.Name;
+        ConfigureSeries(series, row);
+        FillSeriesWithRowValues(series, row);
+        chart.Series.Add(series);
+      }
+      ConfigureChartArea(chart.ChartAreas[0]);
+      RecalculateAxesScale(chart.ChartAreas[0]);
+      UpdateYCursorInterval();
+    }
+
+    protected virtual void RemoveScatterPlotDataRows(IEnumerable<ScatterPlotDataRow> rows) {
+      foreach (var row in rows) {
+        DeregisterScatterPlotDataRowEvents(row);
+        Series series = chart.Series[row.Name];
+        chart.Series.Remove(series);
+        if (invisibleSeries.Contains(series))
+          invisibleSeries.Remove(series);
+      }
+      RecalculateAxesScale(chart.ChartAreas[0]);
+    }
+
+    private void ConfigureSeries(Series series, ScatterPlotDataRow row) {
+      series.BorderWidth = 1;
+      series.BorderDashStyle = ChartDashStyle.Solid;
+      series.BorderColor = Color.Empty;
+
+      if (row.VisualProperties.Color != Color.Empty)
+        series.Color = row.VisualProperties.Color;
+      else series.Color = Color.Empty;
+      series.IsVisibleInLegend = row.VisualProperties.IsVisibleInLegend;
+      series.ChartType = SeriesChartType.FastPoint;
+      series.MarkerSize = row.VisualProperties.PointSize;
+      series.MarkerStyle = ConvertPointStyle(row.VisualProperties.PointStyle);
+      series.XAxisType = AxisType.Primary;
+      series.YAxisType = AxisType.Primary;
+
+      if (row.VisualProperties.DisplayName.Trim() != String.Empty) series.LegendText = row.VisualProperties.DisplayName;
+      else series.LegendText = row.Name;
+
+      string xAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.XAxisTitle)
+                      ? "X"
+                      : Content.VisualProperties.XAxisTitle;
+      string yAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.YAxisTitle)
+                            ? "Y"
+                            : Content.VisualProperties.YAxisTitle;
+      series.ToolTip =
+        series.LegendText + Environment.NewLine +
+        xAxisTitle + " = " + "#VALX," + Environment.NewLine +
+        yAxisTitle + " = " + "#VAL";
+    }
+
+    private void ConfigureChartArea(ChartArea area) {
+      if (Content.VisualProperties.TitleFont != null) chart.Titles[0].Font = Content.VisualProperties.TitleFont;
+      if (!Content.VisualProperties.TitleColor.IsEmpty) chart.Titles[0].ForeColor = Content.VisualProperties.TitleColor;
+      chart.Titles[0].Text = Content.VisualProperties.Title;
+
+      if (Content.VisualProperties.AxisTitleFont != null) area.AxisX.TitleFont = Content.VisualProperties.AxisTitleFont;
+      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisX.TitleForeColor = Content.VisualProperties.AxisTitleColor;
+      area.AxisX.Title = Content.VisualProperties.XAxisTitle;
+
+      if (Content.VisualProperties.AxisTitleFont != null) area.AxisY.TitleFont = Content.VisualProperties.AxisTitleFont;
+      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisY.TitleForeColor = Content.VisualProperties.AxisTitleColor;
+      area.AxisY.Title = Content.VisualProperties.YAxisTitle;
+    }
+
+    private void RecalculateAxesScale(ChartArea area) {
+      // Reset the axes bounds so that RecalculateAxesScale() will assign new bounds
+      foreach (Axis a in area.Axes) {
+        a.Minimum = double.NaN;
+        a.Maximum = double.NaN;
+      }
+      area.RecalculateAxesScale();
+      area.AxisX.IsMarginVisible = false;
+
+      if (!Content.VisualProperties.XAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.XAxisMinimumFixedValue)) area.AxisX.Minimum = Content.VisualProperties.XAxisMinimumFixedValue;
+      if (!Content.VisualProperties.XAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.XAxisMaximumFixedValue)) area.AxisX.Maximum = Content.VisualProperties.XAxisMaximumFixedValue;
+      if (!Content.VisualProperties.YAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.YAxisMinimumFixedValue)) area.AxisY.Minimum = Content.VisualProperties.YAxisMinimumFixedValue;
+      if (!Content.VisualProperties.YAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.YAxisMaximumFixedValue)) area.AxisY.Maximum = Content.VisualProperties.YAxisMaximumFixedValue;
+      if (area.AxisX.Minimum >= area.AxisX.Maximum) area.AxisX.Maximum = area.AxisX.Minimum + 1;
+      if (area.AxisY.Minimum >= area.AxisY.Maximum) area.AxisY.Maximum = area.AxisY.Minimum + 1;
+    }
+
+    protected virtual void UpdateYCursorInterval() {
+      double interestingValuesRange = (
+        from series in chart.Series
+        where series.Enabled
+        let values = (from point in series.Points
+                      where !point.IsEmpty
+                      select point.YValues[0]).DefaultIfEmpty(1.0)
+        let range = values.Max() - values.Min()
+        where range > 0.0
+        select range
+        ).DefaultIfEmpty(1.0).Min();
+
+      double digits = (int)Math.Log10(interestingValuesRange) - 3;
+      double yZoomInterval = Math.Pow(10, digits);
+      this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval;
+    }
+
+    #region Event Handlers
+    #region Content Event Handlers
+    protected override void Content_NameChanged(object sender, EventArgs e) {
+      if (InvokeRequired)
+        Invoke(new EventHandler(Content_NameChanged), sender, e);
+      else {
+        chart.Titles[0].Text = Content.Name;
+        base.Content_NameChanged(sender, e);
+      }
+    }
+    private void Content_VisualPropertiesChanged(object sender, EventArgs e) {
+      if (InvokeRequired)
+        Invoke(new EventHandler(Content_VisualPropertiesChanged), sender, e);
+      else {
+        ConfigureChartArea(chart.ChartAreas[0]);
+        RecalculateAxesScale(chart.ChartAreas[0]); // axes min/max could have changed
+      }
+    }
+    #endregion
+    #region Rows Event Handlers
+    private void Rows_ItemsAdded(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
+      if (InvokeRequired)
+        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsAdded), sender, e);
+      else {
+        AddScatterPlotDataRows(e.Items);
+      }
+    }
+    private void Rows_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
+      if (InvokeRequired)
+        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsRemoved), sender, e);
+      else {
+        RemoveScatterPlotDataRows(e.Items);
+      }
+    }
+    private void Rows_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
+      if (InvokeRequired)
+        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsReplaced), sender, e);
+      else {
+        RemoveScatterPlotDataRows(e.OldItems);
+        AddScatterPlotDataRows(e.Items);
+      }
+    }
+    private void Rows_CollectionReset(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
+      if (InvokeRequired)
+        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_CollectionReset), sender, e);
+      else {
+        RemoveScatterPlotDataRows(e.OldItems);
+        AddScatterPlotDataRows(e.Items);
+      }
+    }
+    #endregion
+    #region Row Event Handlers
+    private void Row_VisualPropertiesChanged(object sender, EventArgs e) {
+      if (InvokeRequired)
+        Invoke(new EventHandler(Row_VisualPropertiesChanged), sender, e);
+      else {
+        ScatterPlotDataRow row = (ScatterPlotDataRow)sender;
+        Series series = chart.Series[row.Name];
+        series.Points.Clear();
+        ConfigureSeries(series, row);
+        FillSeriesWithRowValues(series, row);
+        RecalculateAxesScale(chart.ChartAreas[0]);
+      }
+    }
+    private void Row_NameChanged(object sender, EventArgs e) {
+      if (InvokeRequired)
+        Invoke(new EventHandler(Row_NameChanged), sender, e);
+      else {
+        ScatterPlotDataRow row = (ScatterPlotDataRow)sender;
+        chart.Series[row.Name].Name = row.Name;
+      }
+    }
+    #endregion
+    #region Points Event Handlers
+    private void Points_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
+      if (InvokeRequired)
+        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded), sender, e);
+      else {
+        ScatterPlotDataRow row = null;
+        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
+        if (row != null) {
+          Series rowSeries = chart.Series[row.Name];
+          if (!invisibleSeries.Contains(rowSeries)) {
+            rowSeries.Points.Clear();
+            FillSeriesWithRowValues(rowSeries, row);
+            RecalculateAxesScale(chart.ChartAreas[0]);
+            UpdateYCursorInterval();
+          }
+        }
+      }
+    }
+    private void Points_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
+      if (InvokeRequired)
+        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved), sender, e);
+      else {
+        ScatterPlotDataRow row = null;
+        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
+        if (row != null) {
+          Series rowSeries = chart.Series[row.Name];
+          if (!invisibleSeries.Contains(rowSeries)) {
+            rowSeries.Points.Clear();
+            FillSeriesWithRowValues(rowSeries, row);
+            RecalculateAxesScale(chart.ChartAreas[0]);
+            UpdateYCursorInterval();
+          }
+        }
+      }
+    }
+    private void Points_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
+      if (InvokeRequired)
+        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced), sender, e);
+      else {
+        ScatterPlotDataRow row = null;
+        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
+        if (row != null) {
+          Series rowSeries = chart.Series[row.Name];
+          if (!invisibleSeries.Contains(rowSeries)) {
+            rowSeries.Points.Clear();
+            FillSeriesWithRowValues(rowSeries, row);
+            RecalculateAxesScale(chart.ChartAreas[0]);
+            UpdateYCursorInterval();
+          }
+        }
+      }
+    }
+    private void Points_CollectionReset(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
+      if (InvokeRequired)
+        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset), sender, e);
+      else {
+        ScatterPlotDataRow row = null;
+        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
+        if (row != null) {
+          Series rowSeries = chart.Series[row.Name];
+          if (!invisibleSeries.Contains(rowSeries)) {
+            rowSeries.Points.Clear();
+            FillSeriesWithRowValues(rowSeries, row);
+            RecalculateAxesScale(chart.ChartAreas[0]);
+            UpdateYCursorInterval();
+          }
+        }
+      }
+    }
+    #endregion
+    #endregion
+
+    #region Chart Event Handlers
+    private void chart_MouseDown(object sender, MouseEventArgs e) {
+      HitTestResult result = chart.HitTest(e.X, e.Y);
+      if (result.ChartElementType == ChartElementType.LegendItem) {
+        ToggleSeriesVisible(result.Series);
+      }
+    }
+    private void chart_MouseMove(object sender, MouseEventArgs e) {
+      HitTestResult result = chart.HitTest(e.X, e.Y);
+      if (result.ChartElementType == ChartElementType.LegendItem)
+        this.Cursor = Cursors.Hand;
+      else
+        this.Cursor = Cursors.Default;
+    }
+    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
+      foreach (LegendItem legendItem in e.LegendItems) {
+        var series = chart.Series[legendItem.SeriesName];
+        if (series != null) {
+          bool seriesIsInvisible = invisibleSeries.Contains(series);
+          foreach (LegendCell cell in legendItem.Cells) {
+            cell.ForeColor = seriesIsInvisible ? Color.Gray : Color.Black;
+          }
+        }
+      }
+    }
+    #endregion
+
+    private void ToggleSeriesVisible(Series series) {
+      if (!invisibleSeries.Contains(series)) {
+        series.Points.Clear();
+        invisibleSeries.Add(series);
       } else {
-        ClearChart();
-      }
-    }
-
-    private void Content_AxisNameChanged(object sender, EventArgs e) {
-      ConfigureChart();
-    }
-
-    private void ConfigureChart() {
-      if (InvokeRequired) {
-        Invoke((Action)ConfigureChart);
-      } else {
-        chart.ChartAreas[0].AxisX.Title = Content.XAxisName;
-        chart.ChartAreas[0].AxisY.Title = Content.YAxisName;
-        chart.Titles[0].Text = Content.Name;
-      }
-    }
-
-    private void ClearChart() {
-      if (InvokeRequired) {
-        Invoke((Action)ClearChart);
-      } else {
-        chart.Series[0].Points.Clear();
-        chart.ChartAreas[0].AxisX.Title = String.Empty;
-        chart.ChartAreas[0].AxisY.Title = String.Empty;
-        chart.Titles[0].Text = String.Empty;
-      }
-    }
-
-    private void RedrawChart() {
-      if (InvokeRequired) {
-        Invoke((Action)RedrawChart);
-      } else {
-        chart.Series[0].Points.SuspendUpdates();
-        chart.Series[0].Points.Clear();
-        foreach (var p in Content.Points.ToArray()) {
-          chart.Series[0].Points.AddXY(p.X, p.Y);
-        }
-        chart.Series[0].Points.ResumeUpdates();
-      }
-    }
+        invisibleSeries.Remove(series);
+        if (Content != null) {
+
+          var row = (from r in Content.Rows
+                     where r.Name == series.Name
+                     select r).Single();
+          FillSeriesWithRowValues(series, row);
+          this.chart.Legends[series.Legend].ForeColor = Color.Black;
+          RecalculateAxesScale(chart.ChartAreas[0]);
+          UpdateYCursorInterval();
+        }
+      }
+    }
+
+    private void FillSeriesWithRowValues(Series series, ScatterPlotDataRow row) {
+      for (int i = 0; i < row.Points.Count; i++) {
+        var value = row.Points[i];
+        DataPoint point = new DataPoint();
+        if (IsInvalidValue(value.X) || IsInvalidValue(value.Y))
+          point.IsEmpty = true;
+        else {
+          point.XValue = value.X;
+          point.YValues = new double[] { value.Y };
+        }
+        series.Points.Add(point);
+      }
+    }
+
+    #region Helpers
+    public static IEnumerable<double> DoubleRange(double min, double max, double step) {
+      double i;
+      for (i = min; i <= max; i += step)
+        yield return i;
+
+      if (i != max + step)
+        yield return i;
+    }
+
+    private double HumanRoundRange(double range) {
+      double base10 = Math.Pow(10.0, Math.Floor(Math.Log10(range)));
+      double rounding = range / base10;
+      if (rounding <= 1.5) rounding = 1;
+      else if (rounding <= 2.25) rounding = 2;
+      else if (rounding <= 3.75) rounding = 2.5;
+      else if (rounding <= 7.5) rounding = 5;
+      else rounding = 10;
+      return rounding * base10;
+    }
+
+    private double HumanRoundMax(double max) {
+      double base10;
+      if (max > 0) base10 = Math.Pow(10.0, Math.Floor(Math.Log10(max)));
+      else base10 = Math.Pow(10.0, Math.Ceiling(Math.Log10(-max)));
+      double rounding = (max > 0) ? base10 : -base10;
+      while (rounding < max) rounding += base10;
+      return rounding;
+    }
+
+    private MarkerStyle ConvertPointStyle(ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle pointStyle) {
+      switch (pointStyle) {
+        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Circle:
+          return MarkerStyle.Circle;
+        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Cross:
+          return MarkerStyle.Cross;
+        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Diamond:
+          return MarkerStyle.Diamond;
+        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Square:
+          return MarkerStyle.Square;
+        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star4:
+          return MarkerStyle.Star4;
+        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star5:
+          return MarkerStyle.Star5;
+        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star6:
+          return MarkerStyle.Star6;
+        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star10:
+          return MarkerStyle.Star10;
+        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Triangle:
+          return MarkerStyle.Triangle;
+        default:
+          return MarkerStyle.None;
+      }
+    }
+
+    protected static bool IsInvalidValue(double x) {
+      return double.IsNaN(x) || x < (double)decimal.MinValue || x > (double)decimal.MaxValue;
+    }
+    #endregion
   }
 }
Index: /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/DataTable.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/DataTable.cs	(revision 8279)
+++ /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/DataTable.cs	(revision 8280)
@@ -59,4 +59,9 @@
     public NamedItemCollection<DataRow> Rows {
       get { return rows; }
+      private set {
+        if (rows != null) throw new InvalidOperationException("Rows already set");
+        rows = value;
+        if (rows != null) RegisterRowsEvents();
+      }
     }
 
@@ -73,5 +78,5 @@
     private IEnumerable<DataRow> StorableRows {
       get { return rows; }
-      set { rows = new NamedItemCollection<DataRow>(value); }
+      set { Rows = new NamedItemCollection<DataRow>(value); }
     }
     #endregion
@@ -81,25 +86,21 @@
     protected DataTable(DataTable original, Cloner cloner)
       : base(original, cloner) {
-      this.VisualProperties = (DataTableVisualProperties)cloner.Clone(original.visualProperties);
-      this.rows = cloner.Clone(original.rows);
-      this.RegisterRowsEvents();
+      VisualProperties = (DataTableVisualProperties)cloner.Clone(original.visualProperties);
+      Rows = cloner.Clone(original.rows);
     }
     public DataTable()
       : base() {
       VisualProperties = new DataTableVisualProperties();
-      rows = new NamedItemCollection<DataRow>();
-      this.RegisterRowsEvents();
+      Rows = new NamedItemCollection<DataRow>();
     }
     public DataTable(string name)
       : base(name) {
       VisualProperties = new DataTableVisualProperties(name);
-      rows = new NamedItemCollection<DataRow>();
-      this.RegisterRowsEvents();
+      Rows = new NamedItemCollection<DataRow>();
     }
     public DataTable(string name, string description)
       : base(name, description) {
       VisualProperties = new DataTableVisualProperties(name);
-      rows = new NamedItemCollection<DataRow>();
-      this.RegisterRowsEvents();
+      Rows = new NamedItemCollection<DataRow>();
     }
 
@@ -217,5 +218,5 @@
     }
     IEnumerable<string> IStringConvertibleMatrix.RowNames {
-      get { return new List<string>(); }
+      get { return Enumerable.Empty<string>(); }
       set { throw new NotSupportedException(); }
     }
Index: /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlot.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlot.cs	(revision 8279)
+++ /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlot.cs	(revision 8280)
@@ -22,4 +22,5 @@
 using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Drawing;
 using System.Linq;
@@ -38,31 +39,43 @@
     }
 
-    [Storable]
-    private ObservableList<PointF> points;
-    public ObservableList<PointF> Points {
-      get { return points; }
-    }
-
-    [Storable]
-    private string xAxisName;
-    public string XAxisName {
-      get { return xAxisName; }
+    private ScatterPlotVisualProperties visualProperties;
+    public ScatterPlotVisualProperties VisualProperties {
+      get { return visualProperties; }
       set {
-        if (value == xAxisName) return;
-        xAxisName = value;
-        OnAxesNameChanged();
+        if (visualProperties != value) {
+          if (value == null) throw new ArgumentNullException("VisualProperties");
+          if (visualProperties != null) visualProperties.PropertyChanged -= new PropertyChangedEventHandler(VisualProperties_PropertyChanged);
+          visualProperties = value;
+          visualProperties.PropertyChanged += new PropertyChangedEventHandler(VisualProperties_PropertyChanged);
+          OnVisualPropertiesChanged();
+        }
       }
     }
 
-    [Storable]
-    private string yAxisName;
-    public string YAxisName {
-      get { return yAxisName; }
+    private NamedItemCollection<ScatterPlotDataRow> rows;
+    public NamedItemCollection<ScatterPlotDataRow> Rows {
+      get { return rows; }
+      private set {
+        if (rows != null) throw new InvalidOperationException("Rows already set");
+        rows = value;
+        if (rows != null) RegisterRowsEvents();
+      }
+    }
+
+    #region Persistence Properties
+    [Storable(Name = "VisualProperties")]
+    private ScatterPlotVisualProperties StorableVisualProperties {
+      get { return visualProperties; }
       set {
-        if (value == yAxisName) return;
-        yAxisName = value;
-        OnAxesNameChanged();
+        visualProperties = value;
+        visualProperties.PropertyChanged += new PropertyChangedEventHandler(VisualProperties_PropertyChanged);
       }
     }
+    [Storable(Name = "Rows")]
+    private IEnumerable<ScatterPlotDataRow> StorableRows {
+      get { return rows; }
+      set { Rows = new NamedItemCollection<ScatterPlotDataRow>(value); }
+    }
+    #endregion
 
     [StorableConstructor]
@@ -70,44 +83,49 @@
     protected ScatterPlot(ScatterPlot original, Cloner cloner)
       : base(original, cloner) {
-      points = new ObservableList<PointF>(original.points);
-      xAxisName = original.xAxisName;
-      yAxisName = original.yAxisName;
+      VisualProperties = cloner.Clone(original.visualProperties);
+      Rows = cloner.Clone(original.rows);
     }
     public ScatterPlot()
       : base() {
-      this.points = new ObservableList<PointF>();
-    }
-    public ScatterPlot(string name)
-      : base(name) {
-      this.points = new ObservableList<PointF>();
+      VisualProperties = new ScatterPlotVisualProperties();
+      Rows = new NamedItemCollection<ScatterPlotDataRow>();
     }
     public ScatterPlot(string name, string description)
       : base(name, description) {
-      this.points = new ObservableList<PointF>();
-    }
-    public ScatterPlot(string name, string description, string xAxisName, string yAxisName)
+      VisualProperties = new ScatterPlotVisualProperties(name);
+      Rows = new NamedItemCollection<ScatterPlotDataRow>();
+    }
+    public ScatterPlot(string name, string description, ScatterPlotVisualProperties visualProperties)
       : base(name, description) {
-      this.points = new ObservableList<PointF>();
-      this.xAxisName = xAxisName;
-      this.yAxisName = yAxisName;
-    }
-    public ScatterPlot(IEnumerable<PointF> points)
-      : base() {
-      this.points = new ObservableList<PointF>(points);
-    }
-    public ScatterPlot(IEnumerable<PointF> points, string name)
-      : base(name) {
-      this.points = new ObservableList<PointF>(points);
-    }
-    public ScatterPlot(IEnumerable<PointF> points, string name, string description)
-      : base(name, description) {
-      this.points = new ObservableList<PointF>(points);
-    }
-    public ScatterPlot(IEnumerable<PointF> points, string name, string description, string xAxisName, string yAxisName)
-      : base(name, description) {
-      this.points = new ObservableList<PointF>(points);
-      this.xAxisName = xAxisName;
-      this.yAxisName = yAxisName;
-    }
+      VisualProperties = visualProperties;
+      Rows = new NamedItemCollection<ScatterPlotDataRow>();
+    }
+
+    // BackwardsCompatibility3.3
+    #region Backwards compatible code, remove with 3.4
+    private ObservableList<PointF> points;
+    [Storable(Name = "points")]
+    private ObservableList<PointF> StorablePoints {
+      set { points = value; }
+    }
+    private string xAxisName;
+    [Storable(Name = "xAxisName")]
+    private string StorableXAxisName {
+      set { xAxisName = value; }
+    }
+    private string yAxisName;
+    [Storable(Name = "yAxisName")]
+    private string StorableYAxisName {
+      set { yAxisName = value; }
+    }
+    [StorableHook(HookType.AfterDeserialization)]
+    private void AfterDeserialization() {
+      if (VisualProperties == null) VisualProperties = new ScatterPlotVisualProperties(name);
+      if (string.IsNullOrEmpty(VisualProperties.XAxisTitle) && !string.IsNullOrEmpty(xAxisName)) VisualProperties.XAxisTitle = xAxisName;
+      if (string.IsNullOrEmpty(VisualProperties.YAxisTitle) && !string.IsNullOrEmpty(yAxisName)) VisualProperties.YAxisTitle = yAxisName;
+      if (rows == null) Rows = new NamedItemCollection<ScatterPlotDataRow>();
+      if ((Rows.Count == 0) && (points != null)) Rows.Add(new ScatterPlotDataRow(name, null, points.Select(p => new Point2D<double>(p.X, p.Y))));
+    }
+    #endregion
 
     public override IDeepCloneable Clone(Cloner cloner) {
@@ -115,23 +133,100 @@
     }
 
-    public event EventHandler AxisNameChanged;
-    protected virtual void OnAxesNameChanged() {
-      EventHandler handler = AxisNameChanged;
-      if (handler != null)
-        handler(this, EventArgs.Empty);
+    public event EventHandler VisualPropertiesChanged;
+    protected virtual void OnVisualPropertiesChanged() {
+      EventHandler handler = VisualPropertiesChanged;
+      if (handler != null) handler(this, EventArgs.Empty);
+    }
+
+    private void VisualProperties_PropertyChanged(object sender, PropertyChangedEventArgs e) {
+      OnVisualPropertiesChanged();
+    }
+
+    protected virtual void RegisterRowsEvents() {
+      rows.ItemsAdded += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(rows_ItemsAdded);
+      rows.ItemsRemoved += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(rows_ItemsRemoved);
+      rows.ItemsReplaced += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(rows_ItemsReplaced);
+      rows.CollectionReset += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(rows_CollectionReset);
+    }
+    private void rows_ItemsAdded(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
+      foreach (ScatterPlotDataRow row in e.Items)
+        this.RegisterRowEvents(row);
+
+      this.OnColumnsChanged();
+      this.OnColumnNamesChanged();
+      this.OnReset();
+    }
+    private void rows_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
+      foreach (ScatterPlotDataRow row in e.Items)
+        this.DeregisterRowEvents(row);
+
+      this.OnColumnsChanged();
+      this.OnColumnNamesChanged();
+      this.OnReset();
+    }
+    private void rows_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
+      foreach (ScatterPlotDataRow row in e.OldItems)
+        this.DeregisterRowEvents(row);
+      foreach (ScatterPlotDataRow row in e.Items)
+        this.RegisterRowEvents(row);
+
+      this.OnColumnsChanged();
+      this.OnColumnNamesChanged();
+      this.OnReset();
+    }
+    private void rows_CollectionReset(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
+      foreach (ScatterPlotDataRow row in e.OldItems)
+        this.DeregisterRowEvents(row);
+      foreach (ScatterPlotDataRow row in e.Items)
+        this.RegisterRowEvents(row);
+
+      if (e.OldItems.Count() != e.Items.Count())
+        this.OnColumnsChanged();
+      this.OnColumnNamesChanged();
+      this.OnReset();
+    }
+
+    protected virtual void RegisterRowEvents(ScatterPlotDataRow row) {
+      row.Points.ItemsAdded += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded);
+      row.Points.ItemsMoved += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsMoved);
+      row.Points.ItemsRemoved += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved);
+      row.Points.ItemsReplaced += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced);
+      row.Points.CollectionReset += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset);
+    }
+    protected virtual void DeregisterRowEvents(ScatterPlotDataRow row) {
+      row.Points.ItemsAdded -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded);
+      row.Points.ItemsMoved -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsMoved);
+      row.Points.ItemsRemoved -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved);
+      row.Points.ItemsReplaced -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced);
+      row.Points.CollectionReset -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset);
+    }
+
+    private void Points_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
+      this.OnReset();
+    }
+    private void Points_ItemsMoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
+      this.OnReset();
+    }
+    private void Points_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
+      this.OnReset();
+    }
+    private void Points_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
+      this.OnReset();
+    }
+    private void Points_CollectionReset(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
+      this.OnReset();
     }
 
     #region IStringConvertibleMatrix Members
-
     int IStringConvertibleMatrix.Rows {
-      get { return points.Count; }
+      get { return rows.Count == 0 ? 0 : rows.Max(r => r.Points.Count); }
       set { throw new NotSupportedException(); }
     }
     int IStringConvertibleMatrix.Columns {
-      get { return 2; }
+      get { return rows.Count; }
       set { throw new NotSupportedException(); }
     }
     IEnumerable<string> IStringConvertibleMatrix.ColumnNames {
-      get { return new string[] { xAxisName, yAxisName }; }
+      get { return rows.Select(r => r.Name); }
       set { throw new NotSupportedException(); }
     }
@@ -142,5 +237,5 @@
 
     bool IStringConvertibleMatrix.SortableView {
-      get { return false; }
+      get { return true; }
       set { throw new NotSupportedException(); }
     }
@@ -150,6 +245,8 @@
 
     string IStringConvertibleMatrix.GetValue(int rowIndex, int columnIndex) {
-      if (rowIndex < points.Count && columnIndex < 2) {
-        return columnIndex == 0 ? points[rowIndex].X.ToString() : points[rowIndex].Y.ToString();
+      if (columnIndex < rows.Count) {
+        string columnName = ((IStringConvertibleMatrix)this).ColumnNames.ElementAt(columnIndex);
+        if (rows.ContainsKey(columnName) && rowIndex < rows[columnName].Points.Count)
+          return string.Format("{0};{1}", rows[columnName].Points[rowIndex].X, rows[columnName].Points[rowIndex].Y);
       }
       return string.Empty;
Index: /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotDataRow.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotDataRow.cs	(revision 8280)
+++ /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotDataRow.cs	(revision 8280)
@@ -0,0 +1,108 @@
+#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using HeuristicLab.Collections;
+using HeuristicLab.Common;
+using HeuristicLab.Core;
+using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+
+namespace HeuristicLab.Analysis {
+  /// <summary>
+  /// A row of data values for a scatter plot.
+  /// </summary>
+  [Item("ScatterPlotDataRow", "A row of data values for a scatter plot.")]
+  [StorableClass]
+  public class ScatterPlotDataRow : NamedItem {
+    private ScatterPlotDataRowVisualProperties visualProperties;
+    public ScatterPlotDataRowVisualProperties VisualProperties {
+      get { return visualProperties; }
+      set {
+        if (visualProperties != value) {
+          if (value == null) throw new ArgumentNullException("VisualProperties");
+          if (visualProperties != null) visualProperties.PropertyChanged -= new PropertyChangedEventHandler(VisualProperties_PropertyChanged);
+          visualProperties = value;
+          visualProperties.PropertyChanged += new PropertyChangedEventHandler(VisualProperties_PropertyChanged);
+          OnVisualPropertiesChanged();
+        }
+      }
+    }
+    private ObservableList<Point2D<double>> points;
+    public ObservableList<Point2D<double>> Points {
+      get { return points; }
+    }
+
+    #region Persistence Properties
+    [Storable(Name = "VisualProperties")]
+    private ScatterPlotDataRowVisualProperties StorableVisualProperties {
+      get { return visualProperties; }
+      set {
+        visualProperties = value;
+        visualProperties.PropertyChanged += new PropertyChangedEventHandler(VisualProperties_PropertyChanged);
+      }
+    }
+    [Storable(Name = "Points")]
+    private IEnumerable<Point2D<double>> StorablePoints {
+      get { return points; }
+      set { points = new ObservableList<Point2D<double>>(value); }
+    }
+    #endregion
+
+    [StorableConstructor]
+    protected ScatterPlotDataRow(bool deserializing) : base(deserializing) { }
+    protected ScatterPlotDataRow(ScatterPlotDataRow original, Cloner cloner)
+      : base(original, cloner) {
+      VisualProperties = cloner.Clone(original.visualProperties);
+      points = new ObservableList<Point2D<double>>(original.points);
+    }
+    public ScatterPlotDataRow()
+      : base() {
+      VisualProperties = new ScatterPlotDataRowVisualProperties();
+      points = new ObservableList<Point2D<double>>();
+    }
+    public ScatterPlotDataRow(string name, string description, IEnumerable<Point2D<double>> points)
+      : base(name, description) {
+      VisualProperties = new ScatterPlotDataRowVisualProperties(name);
+      this.points = new ObservableList<Point2D<double>>(points);
+    }
+    public ScatterPlotDataRow(string name, string description, IEnumerable<Point2D<double>> points, ScatterPlotDataRowVisualProperties visualProperties)
+      : base(name, description) {
+      VisualProperties = visualProperties;
+      this.points = new ObservableList<Point2D<double>>(points);
+    }
+
+    public override IDeepCloneable Clone(Cloner cloner) {
+      return new ScatterPlotDataRow(this, cloner);
+    }
+
+    public event EventHandler VisualPropertiesChanged;
+    protected virtual void OnVisualPropertiesChanged() {
+      EventHandler handler = VisualPropertiesChanged;
+      if (handler != null) handler(this, EventArgs.Empty);
+    }
+
+    private void VisualProperties_PropertyChanged(object sender, PropertyChangedEventArgs e) {
+      OnVisualPropertiesChanged();
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotDataRowVisualProperties.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotDataRowVisualProperties.cs	(revision 8280)
+++ /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotDataRowVisualProperties.cs	(revision 8280)
@@ -0,0 +1,164 @@
+#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2012 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.ComponentModel;
+using System.Drawing;
+using HeuristicLab.Common;
+using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+
+namespace HeuristicLab.Analysis {
+  /// <summary>
+  /// Visual properties of a ScatterPlotDataRow.
+  /// </summary>
+  [StorableClass]
+  public class ScatterPlotDataRowVisualProperties : DeepCloneable, INotifyPropertyChanged {
+    #region DotStyle
+    public enum ScatterPlotDataRowPointStyle {
+      Circle,
+      Cross,
+      Diamond,
+      Square,
+      Star4,
+      Star5,
+      Star6,
+      Star10,
+      Triangle
+    }
+    #endregion
+
+    private Color color;
+    public Color Color {
+      get { return color; }
+      set {
+        if (color != value) {
+          color = value;
+          OnPropertyChanged("Color");
+        }
+      }
+    }
+    private ScatterPlotDataRowPointStyle pointStyle;
+    public ScatterPlotDataRowPointStyle PointStyle {
+      get { return pointStyle; }
+      set {
+        if (pointStyle != value) {
+          pointStyle = value;
+          OnPropertyChanged("PointStyle");
+        }
+      }
+    }
+    private int pointSize;
+    public int PointSize {
+      get { return pointSize; }
+      set {
+        if (pointSize != value) {
+          pointSize = value;
+          OnPropertyChanged("PointSize");
+        }
+      }
+    }
+    private bool isVisibleInLegend;
+    public bool IsVisibleInLegend {
+      get { return isVisibleInLegend; }
+      set {
+        if (isVisibleInLegend != value) {
+          isVisibleInLegend = value;
+          OnPropertyChanged("IsVisibleInLegend");
+        }
+      }
+    }
+    private string displayName;
+    public string DisplayName {
+      get { return displayName == null ? String.Empty : displayName; }
+      set {
+        if (displayName != value) {
+          if (value == null && displayName != String.Empty) {
+            displayName = String.Empty;
+            OnPropertyChanged("DisplayName");
+          } else if (value != null) {
+            displayName = value;
+            OnPropertyChanged("DisplayName");
+          }
+        }
+      }
+    }
+
+    #region Persistence Properties
+    [Storable(Name = "Color")]
+    private Color StorableColor {
+      get { return color; }
+      set { color = value; }
+    }
+    [Storable(Name = "PointStyle")]
+    private ScatterPlotDataRowPointStyle StorablePointStyle {
+      get { return pointStyle; }
+      set { pointStyle = value; }
+    }
+    [Storable(Name = "PointSize")]
+    private int StorablePointSize {
+      get { return pointSize; }
+      set { pointSize = value; }
+    }
+    [Storable(Name = "IsVisibleInLegend")]
+    private bool StorableIsVisibleInLegend {
+      get { return isVisibleInLegend; }
+      set { isVisibleInLegend = value; }
+    }
+    [Storable(Name = "DisplayName")]
+    private string StorableDisplayName {
+      get { return displayName; }
+      set { displayName = value; }
+    }
+    #endregion
+
+    [StorableConstructor]
+    protected ScatterPlotDataRowVisualProperties(bool deserializing) : base() { }
+    protected ScatterPlotDataRowVisualProperties(ScatterPlotDataRowVisualProperties original, Cloner cloner)
+      : base(original, cloner) {
+      this.color = original.color;
+      this.pointStyle = original.pointStyle;
+      this.pointSize = original.pointSize;
+      this.displayName = original.displayName;
+      this.isVisibleInLegend = original.isVisibleInLegend;
+    }
+    public ScatterPlotDataRowVisualProperties() {
+      color = Color.Empty;
+      pointStyle = ScatterPlotDataRowPointStyle.Circle;
+      pointSize = 3;
+      displayName = String.Empty;
+      isVisibleInLegend = true;
+    }
+    public ScatterPlotDataRowVisualProperties(string displayName)
+      : this() {
+      this.displayName = displayName;
+    }
+
+    public override IDeepCloneable Clone(Cloner cloner) {
+      return new ScatterPlotDataRowVisualProperties(this, cloner);
+    }
+
+    public event PropertyChangedEventHandler PropertyChanged;
+    protected virtual void OnPropertyChanged(string propertyName) {
+      PropertyChangedEventHandler handler = PropertyChanged;
+      if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotHistory.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotHistory.cs	(revision 8280)
+++ /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotHistory.cs	(revision 8280)
@@ -0,0 +1,49 @@
+#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2012 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.Collections.Generic;
+using System.Drawing;
+using HeuristicLab.Common;
+using HeuristicLab.Core;
+using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+
+namespace HeuristicLab.Analysis {
+  /// <summary>
+  /// Represents history values of scatter plots.
+  /// </summary>
+  [Item("ScatterPlotHistory", "Represents history values of scatter plots.")]
+  [StorableClass]
+  public class ScatterPlotHistory : ItemCollection<ScatterPlot> {
+    public static new Image StaticItemImage {
+      get { return HeuristicLab.Common.Resources.VSImageLibrary.Cab; }
+    }
+
+    [StorableConstructor]
+    protected ScatterPlotHistory(bool deserializing) : base(deserializing) { }
+    protected ScatterPlotHistory(ScatterPlotHistory original, Cloner cloner) : base(original, cloner) { }
+    public ScatterPlotHistory() : base() { }
+    public ScatterPlotHistory(IEnumerable<ScatterPlot> scatterPlots) : base(new ItemCollection<ScatterPlot>(scatterPlots)) { }
+
+    public override IDeepCloneable Clone(Cloner cloner) {
+      return new ScatterPlotHistory(this, cloner);
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotVisualProperties.cs
===================================================================
--- /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotVisualProperties.cs	(revision 8280)
+++ /trunk/sources/HeuristicLab.Analysis/3.3/DataVisualization/ScatterPlotVisualProperties.cs	(revision 8280)
@@ -0,0 +1,329 @@
+#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2012 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.ComponentModel;
+using System.Drawing;
+using HeuristicLab.Common;
+using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+
+namespace HeuristicLab.Analysis {
+  /// <summary>
+  /// Visual properties of a ScatterPlot.
+  /// </summary>
+  [StorableClass]
+  public class ScatterPlotVisualProperties : DeepCloneable, INotifyPropertyChanged {
+    private Font titleFont;
+    public Font TitleFont {
+      get { return titleFont; }
+      set {
+        if (titleFont == null || value == null
+          || titleFont.Name != value.Name || titleFont.Size != value.Size || titleFont.Style != value.Style) {
+          titleFont = value;
+          OnPropertyChanged("TitleFont");
+        }
+      }
+    }
+    private Color titleColor;
+    public Color TitleColor {
+      get { return titleColor; }
+      set {
+        if (titleColor != value) {
+          titleColor = value;
+          OnPropertyChanged("TitleFontColor");
+        }
+      }
+    }
+    private string title;
+    public string Title {
+      get { return title; }
+      set {
+        if (title != value) {
+          title = value;
+          OnPropertyChanged("Title");
+        }
+      }
+    }
+    private Font axisTitleFont;
+    public Font AxisTitleFont {
+      get { return axisTitleFont; }
+      set {
+        if (axisTitleFont == null || axisTitleFont == null
+          || axisTitleFont.Name != value.Name || axisTitleFont.Size != value.Size || axisTitleFont.Style != value.Style) {
+          axisTitleFont = value;
+          OnPropertyChanged("AxisTitleFont");
+        }
+      }
+    }
+    private Color axisTitleColor;
+    public Color AxisTitleColor {
+      get { return axisTitleColor; }
+      set {
+        if (axisTitleColor != value) {
+          axisTitleColor = value;
+          OnPropertyChanged("AxisTitleColor");
+        }
+      }
+    }
+    private string xAxisTitle;
+    public string XAxisTitle {
+      get { return xAxisTitle; }
+      set {
+        if (value == null) value = string.Empty;
+        if (xAxisTitle != value) {
+          xAxisTitle = value;
+          OnPropertyChanged("XAxisTitle");
+        }
+      }
+    }
+
+    private string yAxisTitle;
+    public string YAxisTitle {
+      get { return yAxisTitle; }
+      set {
+        if (value == null) value = string.Empty;
+        if (yAxisTitle != value) {
+          yAxisTitle = value;
+          OnPropertyChanged("YAxisTitle");
+        }
+      }
+    }
+
+    private bool xAxisMinimumAuto;
+    public bool XAxisMinimumAuto {
+      get { return xAxisMinimumAuto; }
+      set {
+        if (xAxisMinimumAuto != value) {
+          xAxisMinimumAuto = value;
+          if (value) xAxisMinimumFixedValue = double.NaN;
+          OnPropertyChanged("XAxisMinimumAuto");
+        }
+      }
+    }
+
+    private bool xAxisMaximumAuto;
+    public bool XAxisMaximumAuto {
+      get { return xAxisMaximumAuto; }
+      set {
+        if (xAxisMaximumAuto != value) {
+          xAxisMaximumAuto = value;
+          if (value) xAxisMaximumFixedValue = double.NaN;
+          OnPropertyChanged("XAxisMaximumAuto");
+        }
+      }
+    }
+
+    private double xAxisMinimumFixedValue;
+    public double XAxisMinimumFixedValue {
+      get { return xAxisMinimumFixedValue; }
+      set {
+        if (xAxisMinimumFixedValue != value) {
+          xAxisMinimumFixedValue = value;
+          OnPropertyChanged("XAxisMinimumFixedValue");
+        }
+      }
+    }
+
+    private double xAxisMaximumFixedValue;
+    public double XAxisMaximumFixedValue {
+      get { return xAxisMaximumFixedValue; }
+      set {
+        if (xAxisMaximumFixedValue != value) {
+          xAxisMaximumFixedValue = value;
+          OnPropertyChanged("XAxisMaximumFixedValue");
+        }
+      }
+    }
+
+    private bool yAxisMinimumAuto;
+    public bool YAxisMinimumAuto {
+      get { return yAxisMinimumAuto; }
+      set {
+        if (yAxisMinimumAuto != value) {
+          yAxisMinimumAuto = value;
+          if (value) yAxisMinimumFixedValue = double.NaN;
+          OnPropertyChanged("YAxisMinimumAuto");
+        }
+      }
+    }
+
+    private bool yAxisMaximumAuto;
+    public bool YAxisMaximumAuto {
+      get { return yAxisMaximumAuto; }
+      set {
+        if (yAxisMaximumAuto != value) {
+          yAxisMaximumAuto = value;
+          if (value) yAxisMaximumFixedValue = double.NaN;
+          OnPropertyChanged("YAxisMaximumAuto");
+        }
+      }
+    }
+
+    private double yAxisMinimumFixedValue;
+    public double YAxisMinimumFixedValue {
+      get { return yAxisMinimumFixedValue; }
+      set {
+        if (yAxisMinimumFixedValue != value) {
+          yAxisMinimumFixedValue = value;
+          OnPropertyChanged("YAxisMinimumFixedValue");
+        }
+      }
+    }
+
+    private double yAxisMaximumFixedValue;
+    public double YAxisMaximumFixedValue {
+      get { return yAxisMaximumFixedValue; }
+      set {
+        if (yAxisMaximumFixedValue != value) {
+          yAxisMaximumFixedValue = value;
+          OnPropertyChanged("YAxisMaximumFixedValue");
+        }
+      }
+    }
+
+    #region Persistence Properties
+    [Storable(Name = "TitleFont")]
+    private Font StorableTitleFont {
+      get { return titleFont; }
+      set { titleFont = value; }
+    }
+    [Storable(Name = "TitleColor")]
+    private Color StorableTitleColor {
+      get { return titleColor; }
+      set { titleColor = value; }
+    }
+    [Storable(Name = "Title")]
+    private string StorableTitle {
+      get { return title; }
+      set { title = value; }
+    }
+    [Storable(Name = "AxisTitleFont")]
+    private Font StorableAxisTitleFont {
+      get { return axisTitleFont; }
+      set { axisTitleFont = value; }
+    }
+    [Storable(Name = "AxisTitleColor")]
+    private Color StorableAxisTitleColor {
+      get { return axisTitleColor; }
+      set { axisTitleColor = value; }
+    }
+    [Storable(Name = "XAxisTitle")]
+    private string StorableXAxisTitle {
+      get { return xAxisTitle; }
+      set { xAxisTitle = value; }
+    }
+    [Storable(Name = "YAxisTitle")]
+    private string StorableYAxisTitle {
+      get { return yAxisTitle; }
+      set { yAxisTitle = value; }
+    }
+    [Storable(Name = "XAxisMinimumAuto")]
+    private bool StorableXAxisMinimumAuto {
+      get { return xAxisMinimumAuto; }
+      set { xAxisMinimumAuto = value; }
+    }
+    [Storable(Name = "XAxisMaximumAuto")]
+    private bool StorableXAxisMaximumAuto {
+      get { return xAxisMaximumAuto; }
+      set { xAxisMaximumAuto = value; }
+    }
+    [Storable(Name = "XAxisMinimumFixedValue")]
+    private double StorableXAxisMinimumFixedValue {
+      get { return xAxisMinimumFixedValue; }
+      set { xAxisMinimumFixedValue = value; }
+    }
+    [Storable(Name = "XAxisMaximumFixedValue")]
+    private double StorableXAxisMaximumFixedValue {
+      get { return xAxisMaximumFixedValue; }
+      set { xAxisMaximumFixedValue = value; }
+    }
+    [Storable(Name = "YAxisMinimumAuto")]
+    private bool StorableYAxisMinimumAuto {
+      get { return yAxisMinimumAuto; }
+      set { yAxisMinimumAuto = value; }
+    }
+    [Storable(Name = "YAxisMaximumAuto")]
+    private bool StorableYAxisMaximumAuto {
+      get { return yAxisMaximumAuto; }
+      set { yAxisMaximumAuto = value; }
+    }
+    [Storable(Name = "YAxisMinimumFixedValue")]
+    private double StorableYAxisMinimumFixedValue {
+      get { return yAxisMinimumFixedValue; }
+      set { yAxisMinimumFixedValue = value; }
+    }
+    [Storable(Name = "YAxisMaximumFixedValue")]
+    private double StorableYAxisMaximumFixedValue {
+      get { return yAxisMaximumFixedValue; }
+      set { yAxisMaximumFixedValue = value; }
+    }
+    #endregion
+
+    [StorableConstructor]
+    protected ScatterPlotVisualProperties(bool deserializing) : base() { }
+    protected ScatterPlotVisualProperties(ScatterPlotVisualProperties original, Cloner cloner)
+      : base(original, cloner) {
+      if (original.titleFont != null)
+        this.titleFont = (Font)original.titleFont.Clone();
+      if (original.axisTitleFont != null)
+        this.axisTitleFont = (Font)original.axisTitleFont.Clone();
+      this.title = original.title;
+      this.xAxisTitle = original.xAxisTitle;
+      this.yAxisTitle = original.yAxisTitle;
+      this.xAxisMinimumAuto = original.xAxisMinimumAuto;
+      this.xAxisMinimumFixedValue = original.xAxisMinimumFixedValue;
+      this.xAxisMaximumAuto = original.xAxisMaximumAuto;
+      this.xAxisMaximumFixedValue = original.xAxisMaximumFixedValue;
+      this.yAxisMinimumAuto = original.yAxisMinimumAuto;
+      this.yAxisMinimumFixedValue = original.yAxisMinimumFixedValue;
+      this.yAxisMaximumAuto = original.yAxisMaximumAuto;
+      this.yAxisMaximumFixedValue = original.yAxisMaximumFixedValue;
+    }
+    public ScatterPlotVisualProperties() {
+      this.titleColor = Color.Black;
+      this.axisTitleColor = Color.Black;
+      this.title = string.Empty;
+      this.xAxisTitle = string.Empty;
+      this.yAxisTitle = string.Empty;
+      this.xAxisMinimumAuto = true;
+      this.xAxisMinimumFixedValue = double.NaN;
+      this.xAxisMaximumAuto = true;
+      this.xAxisMaximumFixedValue = double.NaN;
+      this.yAxisMinimumAuto = true;
+      this.yAxisMinimumFixedValue = double.NaN;
+      this.yAxisMaximumAuto = true;
+      this.yAxisMaximumFixedValue = double.NaN;
+    }
+    public ScatterPlotVisualProperties(string title)
+      : this() {
+      this.title = title;
+    }
+
+    public override IDeepCloneable Clone(Cloner cloner) {
+      return new ScatterPlotVisualProperties(this, cloner);
+    }
+
+    public event PropertyChangedEventHandler PropertyChanged;
+    protected virtual void OnPropertyChanged(string propertyName) {
+      PropertyChangedEventHandler handler = PropertyChanged;
+      if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Analysis/3.3/HeuristicLab.Analysis-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Analysis/3.3/HeuristicLab.Analysis-3.3.csproj	(revision 8279)
+++ /trunk/sources/HeuristicLab.Analysis/3.3/HeuristicLab.Analysis-3.3.csproj	(revision 8280)
@@ -125,4 +125,8 @@
     <Compile Include="AlleleFrequencyAnalysis\AlleleFrequencyCollectionHistory.cs" />
     <Compile Include="BestScopeSolutionAnalyzer.cs" />
+    <Compile Include="DataVisualization\ScatterPlotDataRowVisualProperties.cs" />
+    <Compile Include="DataVisualization\ScatterPlotDataRow.cs" />
+    <Compile Include="DataVisualization\ScatterPlotVisualProperties.cs" />
+    <Compile Include="DataVisualization\ScatterPlotHistory.cs" />
     <Compile Include="DataVisualization\DataRow.cs" />
     <Compile Include="DataVisualization\DataRowVisualProperties.cs" />
Index: /trunk/sources/HeuristicLab.Common/3.3/HeuristicLab.Common-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Common/3.3/HeuristicLab.Common-3.3.csproj	(revision 8279)
+++ /trunk/sources/HeuristicLab.Common/3.3/HeuristicLab.Common-3.3.csproj	(revision 8280)
@@ -124,4 +124,5 @@
     <Compile Include="Content\IStorableContent.cs" />
     <Compile Include="Constants.cs" />
+    <Compile Include="Point2D.cs" />
     <Compile Include="EnumerableExtensions.cs" />
     <Compile Include="MatrixExtensions.cs" />
Index: /trunk/sources/HeuristicLab.Common/3.3/Point2D.cs
===================================================================
--- /trunk/sources/HeuristicLab.Common/3.3/Point2D.cs	(revision 8280)
+++ /trunk/sources/HeuristicLab.Common/3.3/Point2D.cs	(revision 8280)
@@ -0,0 +1,71 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2012 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.ComponentModel;
+using System.Globalization;
+
+namespace HeuristicLab.Common {
+  [Serializable]
+  public struct Point2D<T> where T : struct {
+    public static readonly Point2D<T> Empty = new Point2D<T>();
+
+    private T x;
+    public T X {
+      get { return x; }
+    }
+    private T y;
+    public T Y {
+      get { return y; }
+    }
+
+    [Browsable(false)]
+    public bool IsEmpty {
+      get { return Equals(Empty); }
+    }
+
+    public Point2D(T x, T y) {
+      this.x = x;
+      this.y = y;
+    }
+
+    public static bool operator ==(Point2D<T> left, Point2D<T> right) {
+      return left.x.Equals(right.x) && left.y.Equals(right.y);
+    }
+    public static bool operator !=(Point2D<T> left, Point2D<T> right) {
+      return !(left == right);
+    }
+
+    public override bool Equals(object obj) {
+      if (!(obj is Point2D<T>))
+        return false;
+      Point2D<T> point = (Point2D<T>)obj;
+      return x.Equals(point.x) && y.Equals(point.y) && GetType().Equals(point.GetType());
+    }
+    public override int GetHashCode() {
+      return base.GetHashCode();
+    }
+
+    public override string ToString() {
+      return string.Format((IFormatProvider)CultureInfo.CurrentCulture, "{{X={0}, Y={1}}}", new object[2] { x, y });
+    }
+  }
+}
