source: trunk/sources/HeuristicLab.Analysis.Views/3.3/ScatterPlotView.cs @ 14992

Last change on this file since 14992 was 14992, checked in by pfleck, 2 years ago

#2713 Fixed a potential infinite loop that creates infinite datapoints.

File size: 36.5 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Drawing;
25using System.Linq;
26using System.Text;
27using System.Windows.Forms;
28using System.Windows.Forms.DataVisualization.Charting;
29using HeuristicLab.Collections;
30using HeuristicLab.Common;
31using HeuristicLab.Core.Views;
32using HeuristicLab.MainForm;
33
34namespace HeuristicLab.Analysis.Views {
35  [View("ScatterPlot View")]
36  [Content(typeof(ScatterPlot), true)]
37  public partial class ScatterPlotView : NamedItemView, IConfigureableView {
38    protected List<Series> invisibleSeries;
39    protected Dictionary<IObservableList<Point2D<double>>, ScatterPlotDataRow> pointsRowsTable;
40    protected Dictionary<Series, Series> seriesToRegressionSeriesTable;
41    private double xMin, xMax, yMin, yMax;
42
43    public new ScatterPlot Content {
44      get { return (ScatterPlot)base.Content; }
45      set { base.Content = value; }
46    }
47
48    public bool ShowName {
49      get { return nameTextBox.Visible; }
50      set {
51        if (nameTextBox.Visible != value) {
52          foreach (Control c in Controls) {
53            if (c == chart) continue;
54            c.Visible = value;
55          }
56          chart.Dock = value ? DockStyle.None : DockStyle.Fill;
57        }
58      }
59    }
60
61    public ScatterPlotView() {
62      InitializeComponent();
63      pointsRowsTable = new Dictionary<IObservableList<Point2D<double>>, ScatterPlotDataRow>();
64      seriesToRegressionSeriesTable = new Dictionary<Series, Series>();
65      invisibleSeries = new List<Series>();
66      chart.CustomizeAllChartAreas();
67      chart.ChartAreas[0].CursorX.Interval = 1;
68      chart.ContextMenuStrip.Items.Add(configureToolStripMenuItem);
69    }
70
71    #region Event Handler Registration
72    protected override void DeregisterContentEvents() {
73      foreach (ScatterPlotDataRow row in Content.Rows)
74        DeregisterScatterPlotDataRowEvents(row);
75      Content.VisualPropertiesChanged -= new EventHandler(Content_VisualPropertiesChanged);
76      Content.Rows.ItemsAdded -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsAdded);
77      Content.Rows.ItemsRemoved -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsRemoved);
78      Content.Rows.ItemsReplaced -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsReplaced);
79      Content.Rows.CollectionReset -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_CollectionReset);
80    }
81    protected override void RegisterContentEvents() {
82      Content.VisualPropertiesChanged += new EventHandler(Content_VisualPropertiesChanged);
83      Content.Rows.ItemsAdded += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsAdded);
84      Content.Rows.ItemsRemoved += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsRemoved);
85      Content.Rows.ItemsReplaced += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsReplaced);
86      Content.Rows.CollectionReset += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_CollectionReset);
87    }
88
89    protected virtual void RegisterScatterPlotDataRowEvents(ScatterPlotDataRow row) {
90      row.NameChanged += new EventHandler(Row_NameChanged);
91      row.VisualPropertiesChanged += new EventHandler(Row_VisualPropertiesChanged);
92      pointsRowsTable.Add(row.Points, row);
93      row.Points.ItemsAdded += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded);
94      row.Points.ItemsRemoved += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved);
95      row.Points.ItemsReplaced += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced);
96      row.Points.CollectionReset += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset);
97    }
98    protected virtual void DeregisterScatterPlotDataRowEvents(ScatterPlotDataRow row) {
99      row.Points.ItemsAdded -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded);
100      row.Points.ItemsRemoved -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved);
101      row.Points.ItemsReplaced -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced);
102      row.Points.CollectionReset -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset);
103      pointsRowsTable.Remove(row.Points);
104      row.VisualPropertiesChanged -= new EventHandler(Row_VisualPropertiesChanged);
105      row.NameChanged -= new EventHandler(Row_NameChanged);
106    }
107    #endregion
108
109    protected override void OnContentChanged() {
110      base.OnContentChanged();
111      invisibleSeries.Clear();
112      chart.Titles[0].Text = string.Empty;
113      chart.ChartAreas[0].AxisX.Title = string.Empty;
114      chart.ChartAreas[0].AxisY.Title = string.Empty;
115      chart.Series.Clear();
116      if (Content != null) {
117        chart.Titles[0].Text = Content.Name;
118        chart.Titles[0].Visible = !string.IsNullOrEmpty(Content.Name);
119        AddScatterPlotDataRows(Content.Rows);
120        ConfigureChartArea(chart.ChartAreas[0]);
121        RecalculateMinMaxPointValues();
122        RecalculateAxesScale(chart.ChartAreas[0]);
123      }
124    }
125
126    protected override void SetEnabledStateOfControls() {
127      base.SetEnabledStateOfControls();
128      chart.Enabled = Content != null;
129    }
130
131    public void ShowConfiguration() {
132      if (Content != null) {
133        using (ScatterPlotVisualPropertiesDialog dialog = new ScatterPlotVisualPropertiesDialog(Content)) {
134          dialog.ShowDialog(this);
135        }
136      } else MessageBox.Show("Nothing to configure.");
137    }
138
139    protected virtual void AddScatterPlotDataRows(IEnumerable<ScatterPlotDataRow> rows) {
140      foreach (var row in rows) {
141        RegisterScatterPlotDataRowEvents(row);
142        Series series = new Series(row.Name) { Tag = row };
143
144        if (row.VisualProperties.DisplayName.Trim() != String.Empty) series.LegendText = row.VisualProperties.DisplayName;
145        else series.LegendText = row.Name;
146
147        var regressionSeries = new Series(row.Name + "_Regression") {
148          Tag = row,
149          ChartType = SeriesChartType.Line,
150          BorderDashStyle = ChartDashStyle.Dot,
151          IsVisibleInLegend = false,
152          Color = Color.Transparent // to avoid auto color assignment via color palette
153        };
154
155        seriesToRegressionSeriesTable.Add(series, regressionSeries);
156        ConfigureSeries(series, regressionSeries, row);
157        FillSeriesWithRowValues(series, row);
158
159        chart.Series.Add(series);
160        chart.Series.Add(regressionSeries);
161        FillRegressionSeries(regressionSeries, row);
162      }
163      ConfigureChartArea(chart.ChartAreas[0]);
164      RecalculateMinMaxPointValues();
165      RecalculateAxesScale(chart.ChartAreas[0]);
166      UpdateYCursorInterval();
167      UpdateRegressionSeriesColors();
168    }
169
170    protected virtual void RemoveScatterPlotDataRows(IEnumerable<ScatterPlotDataRow> rows) {
171      foreach (var row in rows) {
172        DeregisterScatterPlotDataRowEvents(row);
173        Series series = chart.Series[row.Name];
174        chart.Series.Remove(series);
175        if (invisibleSeries.Contains(series))
176          invisibleSeries.Remove(series);
177        chart.Series.Remove(seriesToRegressionSeriesTable[series]);
178        seriesToRegressionSeriesTable.Remove(series);
179      }
180      RecalculateMinMaxPointValues();
181      RecalculateAxesScale(chart.ChartAreas[0]);
182    }
183
184    private void ConfigureSeries(Series series, Series regressionSeries, ScatterPlotDataRow row) {
185      series.BorderWidth = 1;
186      series.BorderDashStyle = ChartDashStyle.Solid;
187      series.BorderColor = Color.Empty;
188
189      if (row.VisualProperties.Color != Color.Empty)
190        series.Color = row.VisualProperties.Color;
191      else series.Color = Color.Empty;
192      series.IsVisibleInLegend = row.VisualProperties.IsVisibleInLegend;
193      series.ChartType = SeriesChartType.FastPoint;
194      series.MarkerSize = row.VisualProperties.PointSize;
195      series.MarkerStyle = ConvertPointStyle(row.VisualProperties.PointStyle);
196      series.XAxisType = AxisType.Primary;
197      series.YAxisType = AxisType.Primary;
198
199      if (row.VisualProperties.DisplayName.Trim() != String.Empty) series.LegendText = row.VisualProperties.DisplayName;
200      else series.LegendText = row.Name;
201
202      string xAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.XAxisTitle)
203                      ? "X"
204                      : Content.VisualProperties.XAxisTitle;
205      string yAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.YAxisTitle)
206                            ? "Y"
207                            : Content.VisualProperties.YAxisTitle;
208      series.ToolTip =
209        series.LegendText + Environment.NewLine +
210        xAxisTitle + " = " + "#VALX," + Environment.NewLine +
211        yAxisTitle + " = " + "#VAL";
212
213      regressionSeries.BorderWidth = Math.Max(1, row.VisualProperties.PointSize / 2);
214      regressionSeries.IsVisibleInLegend = row.VisualProperties.IsRegressionVisibleInLegend &&
215        row.VisualProperties.RegressionType != ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.None;
216      regressionSeries.LegendText = string.IsNullOrEmpty(row.VisualProperties.RegressionDisplayName)
217        ? string.Format("{0}({1})", row.VisualProperties.RegressionType, row.Name)
218        : row.VisualProperties.RegressionDisplayName;
219    }
220
221    private void ConfigureChartArea(ChartArea area) {
222      if (Content.VisualProperties.TitleFont != null) chart.Titles[0].Font = Content.VisualProperties.TitleFont;
223      if (!Content.VisualProperties.TitleColor.IsEmpty) chart.Titles[0].ForeColor = Content.VisualProperties.TitleColor;
224      chart.Titles[0].Text = Content.VisualProperties.Title;
225      chart.Titles[0].Visible = !string.IsNullOrEmpty(Content.VisualProperties.Title);
226
227      if (Content.VisualProperties.AxisTitleFont != null) area.AxisX.TitleFont = Content.VisualProperties.AxisTitleFont;
228      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisX.TitleForeColor = Content.VisualProperties.AxisTitleColor;
229      area.AxisX.Title = Content.VisualProperties.XAxisTitle;
230      area.AxisX.MajorGrid.Enabled = Content.VisualProperties.XAxisGrid;
231
232      if (Content.VisualProperties.AxisTitleFont != null) area.AxisY.TitleFont = Content.VisualProperties.AxisTitleFont;
233      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisY.TitleForeColor = Content.VisualProperties.AxisTitleColor;
234      area.AxisY.Title = Content.VisualProperties.YAxisTitle;
235      area.AxisY.MajorGrid.Enabled = Content.VisualProperties.YAxisGrid;
236    }
237
238    private void RecalculateAxesScale(ChartArea area) {
239      area.AxisX.Minimum = CalculateMinBound(xMin);
240      area.AxisX.Maximum = CalculateMaxBound(xMax);
241      if (area.AxisX.Minimum == area.AxisX.Maximum) {
242        area.AxisX.Minimum = xMin - 0.5;
243        area.AxisX.Maximum = xMax + 0.5;
244      }
245      area.AxisY.Minimum = CalculateMinBound(yMin);
246      area.AxisY.Maximum = CalculateMaxBound(yMax);
247      if (area.AxisY.Minimum == area.AxisY.Maximum) {
248        area.AxisY.Minimum = yMin - 0.5;
249        area.AxisY.Maximum = yMax + 0.5;
250      }
251      if (xMax - xMin > 0) area.CursorX.Interval = Math.Pow(10, Math.Floor(Math.Log10(area.AxisX.Maximum - area.AxisX.Minimum) - 3));
252      else area.CursorX.Interval = 1;
253      area.AxisX.IsMarginVisible = false;
254
255      if (!Content.VisualProperties.XAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.XAxisMinimumFixedValue)) area.AxisX.Minimum = Content.VisualProperties.XAxisMinimumFixedValue;
256      if (!Content.VisualProperties.XAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.XAxisMaximumFixedValue)) area.AxisX.Maximum = Content.VisualProperties.XAxisMaximumFixedValue;
257      if (!Content.VisualProperties.YAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.YAxisMinimumFixedValue)) area.AxisY.Minimum = Content.VisualProperties.YAxisMinimumFixedValue;
258      if (!Content.VisualProperties.YAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.YAxisMaximumFixedValue)) area.AxisY.Maximum = Content.VisualProperties.YAxisMaximumFixedValue;
259    }
260
261    private static double CalculateMinBound(double min) {
262      if (min == 0) return 0;
263      var scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(min))));
264      return scale * (Math.Floor(min / scale));
265    }
266
267    private static double CalculateMaxBound(double max) {
268      if (max == 0) return 0;
269      var scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(max))));
270      return scale * (Math.Ceiling(max / scale));
271    }
272
273    protected virtual void UpdateYCursorInterval() {
274      double interestingValuesRange = (
275        from series in chart.Series
276        where series.Enabled
277        let values = (from point in series.Points
278                      where !point.IsEmpty
279                      select point.YValues[0]).DefaultIfEmpty(1.0)
280        let range = values.Max() - values.Min()
281        where range > 0.0
282        select range
283        ).DefaultIfEmpty(1.0).Min();
284
285      double digits = (int)Math.Log10(interestingValuesRange) - 3;
286      double yZoomInterval = Math.Pow(10, digits);
287      this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval;
288    }
289
290    protected void UpdateRegressionSeriesColors() {
291      chart.ApplyPaletteColors();
292      foreach (var row in Content.Rows) {
293        var series = chart.Series[row.Name];
294        var regressionSeries = seriesToRegressionSeriesTable[series];
295        regressionSeries.Color = series.Color;
296      }
297    }
298
299    #region Event Handlers
300    #region Content Event Handlers
301    protected override void Content_NameChanged(object sender, EventArgs e) {
302      if (InvokeRequired)
303        Invoke(new EventHandler(Content_NameChanged), sender, e);
304      else {
305        Content.VisualProperties.Title = Content.Name;
306        base.Content_NameChanged(sender, e);
307      }
308    }
309    private void Content_VisualPropertiesChanged(object sender, EventArgs e) {
310      if (InvokeRequired)
311        Invoke(new EventHandler(Content_VisualPropertiesChanged), sender, e);
312      else {
313        ConfigureChartArea(chart.ChartAreas[0]);
314        RecalculateAxesScale(chart.ChartAreas[0]); // axes min/max could have changed
315      }
316    }
317    #endregion
318    #region Rows Event Handlers
319    private void Rows_ItemsAdded(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
320      if (InvokeRequired)
321        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsAdded), sender, e);
322      else {
323        AddScatterPlotDataRows(e.Items);
324      }
325    }
326    private void Rows_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
327      if (InvokeRequired)
328        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsRemoved), sender, e);
329      else {
330        RemoveScatterPlotDataRows(e.Items);
331      }
332    }
333    private void Rows_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
334      if (InvokeRequired)
335        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsReplaced), sender, e);
336      else {
337        RemoveScatterPlotDataRows(e.OldItems);
338        AddScatterPlotDataRows(e.Items);
339      }
340    }
341    private void Rows_CollectionReset(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
342      if (InvokeRequired)
343        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_CollectionReset), sender, e);
344      else {
345        RemoveScatterPlotDataRows(e.OldItems);
346        AddScatterPlotDataRows(e.Items);
347      }
348    }
349    #endregion
350    #region Row Event Handlers
351    private void Row_VisualPropertiesChanged(object sender, EventArgs e) {
352      if (InvokeRequired)
353        Invoke(new EventHandler(Row_VisualPropertiesChanged), sender, e);
354      else {
355        ScatterPlotDataRow row = (ScatterPlotDataRow)sender;
356        Series series = chart.Series[row.Name];
357        Series regressionSeries = seriesToRegressionSeriesTable[series];
358        series.Points.Clear();
359        regressionSeries.Points.Clear();
360        ConfigureSeries(series, regressionSeries, row);
361        FillSeriesWithRowValues(series, row);
362        FillRegressionSeries(regressionSeries, row);
363        RecalculateMinMaxPointValues();
364        RecalculateAxesScale(chart.ChartAreas[0]);
365        UpdateRegressionSeriesColors();
366      }
367    }
368    private void Row_NameChanged(object sender, EventArgs e) {
369      if (InvokeRequired)
370        Invoke(new EventHandler(Row_NameChanged), sender, e);
371      else {
372        ScatterPlotDataRow row = (ScatterPlotDataRow)sender;
373        chart.Series[row.Name].Name = row.Name;
374      }
375    }
376    #endregion
377    #region Points Event Handlers
378    private void Points_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
379      if (InvokeRequired)
380        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded), sender, e);
381      else {
382        ScatterPlotDataRow row = null;
383        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
384        if (row != null) {
385          Series rowSeries = chart.Series[row.Name];
386          Series regressionSeries = seriesToRegressionSeriesTable[rowSeries];
387          if (!invisibleSeries.Contains(rowSeries)) {
388            rowSeries.Points.Clear();
389            regressionSeries.Points.Clear();
390            FillSeriesWithRowValues(rowSeries, row);
391            FillRegressionSeries(regressionSeries, row);
392            RecalculateMinMaxPointValues();
393            RecalculateAxesScale(chart.ChartAreas[0]);
394            UpdateYCursorInterval();
395          }
396        }
397      }
398    }
399    private void Points_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
400      if (InvokeRequired)
401        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved), sender, e);
402      else {
403        ScatterPlotDataRow row = null;
404        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
405        if (row != null) {
406          Series rowSeries = chart.Series[row.Name];
407          Series regressionSeries = seriesToRegressionSeriesTable[rowSeries];
408          if (!invisibleSeries.Contains(rowSeries)) {
409            rowSeries.Points.Clear();
410            regressionSeries.Points.Clear();
411            FillSeriesWithRowValues(rowSeries, row);
412            FillRegressionSeries(regressionSeries, row);
413            RecalculateMinMaxPointValues();
414            RecalculateAxesScale(chart.ChartAreas[0]);
415            UpdateYCursorInterval();
416          }
417        }
418      }
419    }
420    private void Points_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
421      if (InvokeRequired)
422        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced), sender, e);
423      else {
424        ScatterPlotDataRow row = null;
425        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
426        if (row != null) {
427          Series rowSeries = chart.Series[row.Name];
428          Series regressionSeries = seriesToRegressionSeriesTable[rowSeries];
429          if (!invisibleSeries.Contains(rowSeries)) {
430            rowSeries.Points.Clear();
431            regressionSeries.Points.Clear();
432            FillSeriesWithRowValues(rowSeries, row);
433            FillRegressionSeries(regressionSeries, row);
434            RecalculateMinMaxPointValues();
435            RecalculateAxesScale(chart.ChartAreas[0]);
436            UpdateYCursorInterval();
437          }
438        }
439      }
440    }
441    private void Points_CollectionReset(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
442      if (InvokeRequired)
443        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset), sender, e);
444      else {
445        ScatterPlotDataRow row = null;
446        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
447        if (row != null) {
448          Series rowSeries = chart.Series[row.Name];
449          Series regressionSeries = seriesToRegressionSeriesTable[rowSeries];
450          if (!invisibleSeries.Contains(rowSeries)) {
451            rowSeries.Points.Clear();
452            regressionSeries.Points.Clear();
453            FillSeriesWithRowValues(rowSeries, row);
454            FillRegressionSeries(regressionSeries, row);
455            RecalculateMinMaxPointValues();
456            RecalculateAxesScale(chart.ChartAreas[0]);
457            UpdateYCursorInterval();
458          }
459        }
460      }
461    }
462    #endregion
463    private void configureToolStripMenuItem_Click(object sender, System.EventArgs e) {
464      ShowConfiguration();
465    }
466    #endregion
467
468    #region Chart Event Handlers
469    private void chart_MouseDoubleClick(object sender, MouseEventArgs e) {
470      HitTestResult result = chart.HitTest(e.X, e.Y, ChartElementType.DataPoint);
471      if (result.ChartElementType != ChartElementType.DataPoint) return;
472
473      var series = result.Series;
474      var dataPoint = series.Points[result.PointIndex];
475      var tag = dataPoint.Tag;
476      var content = tag as IContent;
477
478      if (tag == null) return;
479      if (content == null) return;
480
481      MainFormManager.MainForm.ShowContent(content);
482    }
483
484    private void chart_MouseDown(object sender, MouseEventArgs e) {
485      HitTestResult result = chart.HitTest(e.X, e.Y);
486      if (result.ChartElementType == ChartElementType.LegendItem) {
487        ToggleSeriesVisible(result.Series);
488      }
489    }
490    private void chart_MouseMove(object sender, MouseEventArgs e) {
491      HitTestResult result = chart.HitTest(e.X, e.Y);
492      if (result.ChartElementType == ChartElementType.LegendItem)
493        this.Cursor = Cursors.Hand;
494      else
495        this.Cursor = Cursors.Default;
496    }
497    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
498      foreach (LegendItem legendItem in e.LegendItems) {
499        var series = chart.Series[legendItem.SeriesName];
500        if (series != null) {
501          bool seriesIsInvisible = invisibleSeries.Contains(series);
502          foreach (LegendCell cell in legendItem.Cells) {
503            cell.ForeColor = seriesIsInvisible ? Color.Gray : Color.Black;
504          }
505        }
506      }
507    }
508    #endregion
509
510    private void ToggleSeriesVisible(Series series) {
511      if (!invisibleSeries.Contains(series)) {
512        series.Points.Clear();
513        invisibleSeries.Add(series);
514        RecalculateMinMaxPointValues();
515      } else {
516        invisibleSeries.Remove(series);
517        if (Content != null) {
518          var row = (ScatterPlotDataRow)series.Tag;
519          if (seriesToRegressionSeriesTable.ContainsKey(series))
520            FillSeriesWithRowValues(series, row);
521          else
522            FillRegressionSeries(series, row);
523          RecalculateMinMaxPointValues();
524          this.chart.Legends[series.Legend].ForeColor = Color.Black;
525          RecalculateAxesScale(chart.ChartAreas[0]);
526          UpdateYCursorInterval();
527        }
528      }
529    }
530
531    private void RecalculateMinMaxPointValues() {
532      yMin = xMin = double.MaxValue;
533      yMax = xMax = double.MinValue;
534      foreach (var s in chart.Series.Where(x => x.Enabled)) {
535        foreach (var p in s.Points) {
536          double x = p.XValue, y = p.YValues[0];
537          if (xMin > x) xMin = x;
538          if (xMax < x) xMax = x;
539          if (yMin > y) yMin = y;
540          if (yMax < y) yMax = y;
541        }
542      }
543    }
544
545    private void FillSeriesWithRowValues(Series series, ScatterPlotDataRow row) {
546      bool zerosOnly = true;
547      for (int i = 0; i < row.Points.Count; i++) {
548        var value = row.Points[i];
549        DataPoint point = new DataPoint();
550        if (IsInvalidValue(value.X) || IsInvalidValue(value.Y))
551          point.IsEmpty = true;
552        else {
553          point.XValue = value.X;
554          point.YValues = new double[] { value.Y };
555        }
556        point.Tag = value.Tag;
557        series.Points.Add(point);
558        if (value.X != 0.0f)
559          zerosOnly = false;
560      }
561      if (zerosOnly) // if all x-values are zero, the x-values are interpreted as 1, 2, 3, ...
562        series.Points.Add(new DataPoint(1, 1) { IsEmpty = true });
563      double correlation = Correlation(row.Points);
564      series.LegendToolTip = string.Format("Correlation (R²) = {0:G4}", correlation * correlation);
565    }
566
567    private void FillRegressionSeries(Series regressionSeries, ScatterPlotDataRow row) {
568      if (row.VisualProperties.RegressionType == ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.None
569        || invisibleSeries.Contains(regressionSeries))
570        return;
571
572      double[] coefficients;
573      if (!Fitting(row, out coefficients)) {
574        regressionSeries.LegendToolTip = "Could not calculate regression.";
575        return;
576      }
577
578      // Fill regrssion series
579      double min = row.Points.Min(p => p.X), max = row.Points.Max(p => p.X);
580      double range = max - min, delta = range / Math.Max(row.Points.Count - 1, 50);
581      if (range > double.Epsilon) {
582        for (double x = min; x <= max; x += delta) {
583          regressionSeries.Points.AddXY(x, Estimate(x, row, coefficients));
584        }
585      }
586
587      // Correlation
588      var data = row.Points.Select(p => new Point2D<double>(p.Y, Estimate(p.X, row, coefficients)));
589      double correlation = Correlation(data.ToList());
590      regressionSeries.LegendToolTip = GetStringFormula(row, coefficients) + Environment.NewLine +
591                                       string.Format("Correlation (R²) = {0:G4}", correlation * correlation);
592      regressionSeries.ToolTip = GetStringFormula(row, coefficients);
593    }
594
595    #region Helpers
596    private MarkerStyle ConvertPointStyle(ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle pointStyle) {
597      switch (pointStyle) {
598        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Circle:
599          return MarkerStyle.Circle;
600        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Cross:
601          return MarkerStyle.Cross;
602        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Diamond:
603          return MarkerStyle.Diamond;
604        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Square:
605          return MarkerStyle.Square;
606        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star4:
607          return MarkerStyle.Star4;
608        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star5:
609          return MarkerStyle.Star5;
610        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star6:
611          return MarkerStyle.Star6;
612        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star10:
613          return MarkerStyle.Star10;
614        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Triangle:
615          return MarkerStyle.Triangle;
616        default:
617          return MarkerStyle.None;
618      }
619    }
620
621    protected static bool IsInvalidValue(double x) {
622      return double.IsNaN(x) || x < (double)decimal.MinValue || x > (double)decimal.MaxValue;
623    }
624    #endregion
625
626    #region Correlation and Fitting Helper
627    protected static double Correlation(IList<Point2D<double>> values) {
628      // sums of x, y, x squared etc.
629      double sx = 0.0;
630      double sy = 0.0;
631      double sxx = 0.0;
632      double syy = 0.0;
633      double sxy = 0.0;
634
635      int n = 0;
636      for (int i = 0; i < values.Count; i++) {
637        double x = values[i].X;
638        double y = values[i].Y;
639        if (IsInvalidValue(x) || IsInvalidValue(y))
640          continue;
641
642        sx += x;
643        sy += y;
644        sxx += x * x;
645        syy += y * y;
646        sxy += x * y;
647        n++;
648      }
649
650      // covariation
651      double cov = sxy / n - sx * sy / n / n;
652      // standard error of x
653      double sigmaX = Math.Sqrt(sxx / n - sx * sx / n / n);
654      // standard error of y
655      double sigmaY = Math.Sqrt(syy / n - sy * sy / n / n);
656
657      // correlation
658      return cov / sigmaX / sigmaY;
659    }
660
661    protected static bool Fitting(ScatterPlotDataRow row, out double[] coefficients) {
662      if (!IsValidRegressionData(row)) {
663        coefficients = new double[0];
664        return false;
665      }
666
667      var xs = row.Points.Select(p => p.X).ToList();
668      var ys = row.Points.Select(p => p.Y).ToList();
669
670      // Input transformations
671      double[,] matrix;
672      int nRows;
673      switch (row.VisualProperties.RegressionType) {
674        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Linear:
675          matrix = CreateMatrix(out nRows, ys, xs);
676          break;
677        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Polynomial:
678          var xss = Enumerable.Range(1, row.VisualProperties.PolynomialRegressionOrder)
679            .Select(o => xs.Select(x => Math.Pow(x, o)).ToList())
680            .Reverse(); // higher order first
681          matrix = CreateMatrix(out nRows, ys, xss.ToArray());
682          break;
683        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Exponential:
684          matrix = CreateMatrix(out nRows, ys.Select(y => Math.Log(y)).ToList(), xs);
685          break;
686        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Power:
687          matrix = CreateMatrix(out nRows, ys.Select(y => Math.Log(y)).ToList(), xs.Select(x => Math.Log(x)).ToList());
688          break;
689        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Logarithmic:
690          matrix = CreateMatrix(out nRows, ys, xs.Select(x => Math.Log(x)).ToList());
691          break;
692        default:
693          throw new ArgumentException("Unknown RegressionType: " + row.VisualProperties.RegressionType);
694      }
695
696      // Linear fitting
697      bool success = LinearFitting(matrix, nRows, out coefficients);
698      if (!success) return false;
699
700      // Output transformation
701      switch (row.VisualProperties.RegressionType) {
702        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Exponential:
703        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Power:
704          coefficients[1] = Math.Exp(coefficients[1]);
705          break;
706      }
707
708      return true;
709    }
710    protected static bool IsValidRegressionData(ScatterPlotDataRow row) {
711      // No invalid values allowed
712      for (int i = 0; i < row.Points.Count; i++) {
713        if (IsInvalidValue(row.Points[i].X) || IsInvalidValue(row.Points[i].Y))
714          return false;
715      }
716      // Exp, Power and Log Regression do not work with negative values
717      switch (row.VisualProperties.RegressionType) {
718        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Exponential:
719          for (int i = 0; i < row.Points.Count; i++) {
720            if (row.Points[i].Y <= 0)
721              return false;
722          }
723          break;
724        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Power:
725          for (int i = 0; i < row.Points.Count; i++) {
726            if (row.Points[i].X <= 0 || row.Points[i].Y <= 0)
727              return false;
728          }
729          break;
730        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Logarithmic:
731          for (int i = 0; i < row.Points.Count; i++) {
732            if (row.Points[i].X <= 0)
733              return false;
734          }
735          break;
736      }
737      return true;
738    }
739    protected static double[,] CreateMatrix(out int nRows, IList<double> ys, params IList<double>[] xss) {
740      var matrix = new double[ys.Count, xss.Length + 1];
741      int rowIdx = 0;
742      for (int i = 0; i < ys.Count; i++) {
743        if (IsInvalidValue(ys[i]) || xss.Any(xs => IsInvalidValue(xs[i])))
744          continue;
745        for (int j = 0; j < xss.Length; j++) {
746          matrix[rowIdx, j] = xss[j][i];
747        }
748        matrix[rowIdx, xss.Length] = ys[i];
749        rowIdx++;
750      }
751      nRows = rowIdx;
752      return matrix;
753    }
754
755    protected static bool LinearFitting(double[,] xsy, int nRows, out double[] coefficients) {
756      int nFeatures = xsy.GetLength(1) - 1;
757
758      alglib.linearmodel lm;
759      alglib.lrreport ar;
760      int retVal;
761      alglib.lrbuild(xsy, nRows, nFeatures, out retVal, out lm, out ar);
762      if (retVal != 1) {
763        coefficients = new double[0];
764        return false;
765      }
766
767      alglib.lrunpack(lm, out coefficients, out nFeatures);
768      return true;
769    }
770
771    protected static double Estimate(double x, ScatterPlotDataRow row, double[] coefficients) {
772      switch (row.VisualProperties.RegressionType) {
773        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Linear:
774          return coefficients[0] * x + coefficients[1];
775        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Polynomial:
776          return coefficients
777            .Reverse() // to match index and order
778            .Select((c, o) => c * Math.Pow(x, o))
779            .Sum();
780        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Exponential:
781          return coefficients[1] * Math.Exp(coefficients[0] * x);
782        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Power:
783          return coefficients[1] * Math.Pow(x, coefficients[0]);
784        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Logarithmic:
785          return coefficients[0] * Math.Log(x) + coefficients[1];
786        default:
787          throw new ArgumentException("Unknown RegressionType: " + row.VisualProperties.RegressionType);
788      }
789    }
790
791    protected static string GetStringFormula(ScatterPlotDataRow row, double[] coefficients) {
792      switch (row.VisualProperties.RegressionType) {
793        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Linear:
794          return string.Format("{0:G4} x {1} {2:G4}", coefficients[0], Sign(coefficients[1]), Math.Abs(coefficients[1]));
795        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Polynomial:
796          var sb = new StringBuilder();
797          sb.AppendFormat("{0:G4}{1}", coefficients[0], PolyFactor(coefficients.Length - 1));
798          foreach (var x in coefficients
799            .Reverse() // match index and order
800            .Select((c, o) => new { c, o })
801            .Reverse() // higher order first
802            .Skip(1)) // highest order poly already added
803            sb.AppendFormat(" {0} {1:G4}{2}", Sign(x.c), Math.Abs(x.c), PolyFactor(x.o));
804          return sb.ToString();
805        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Exponential:
806          return string.Format("{0:G4} e^({1:G4} x)", coefficients[1], coefficients[0]);
807        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Power:
808          return string.Format("{0:G4} x^({1:G4})", coefficients[1], coefficients[0]);
809        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowRegressionType.Logarithmic:
810          return string.Format("{0:G4} ln(x) {1} {2:G4}", coefficients[0], Sign(coefficients[1]), Math.Abs(coefficients[1]));
811        default:
812          throw new ArgumentException("Unknown RegressionType: " + row.VisualProperties.RegressionType);
813      }
814    }
815    private static string Sign(double value) {
816      return value >= 0 ? "+" : "-";
817    }
818    private static string PolyFactor(int order) {
819      if (order == 0) return "";
820      if (order == 1) return " x";
821      if (order == 2) return " x²";
822      if (order == 3) return " x³";
823      return " x^" + order;
824    }
825    #endregion
826  }
827}
Note: See TracBrowser for help on using the repository browser.