source: stable/HeuristicLab.DataPreprocessing.Views/3.4/PreprocessingScatterPlotView.cs @ 13321

Last change on this file since 13321 was 12009, checked in by ascheibe, 7 years ago

#2212 updated copyright year

File size: 20.8 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2015 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.Windows.Forms;
27using System.Windows.Forms.DataVisualization.Charting;
28using HeuristicLab.Analysis;
29using HeuristicLab.Analysis.Views;
30using HeuristicLab.Collections;
31using HeuristicLab.Common;
32using HeuristicLab.Core.Views;
33using HeuristicLab.MainForm;
34
35namespace HeuristicLab.DataPreprocessing.Views {
36  [View("Preprocessing ScatterPlot View")]
37  [Content(typeof(ScatterPlot), false)]
38  public partial class PreprocessingScatterPlotView : ItemView, IConfigureableView {
39    protected List<Series> invisibleSeries;
40    protected Dictionary<IObservableList<Point2D<double>>, ScatterPlotDataRow> pointsRowsTable;
41    private event EventHandler chartDoubleClick;
42
43    public new ScatterPlot Content {
44      get { return (ScatterPlot)base.Content; }
45      set { base.Content = value; }
46    }
47
48    public PreprocessingScatterPlotView() {
49      InitializeComponent();
50      pointsRowsTable = new Dictionary<IObservableList<Point2D<double>>, ScatterPlotDataRow>();
51      invisibleSeries = new List<Series>();
52      chart.CustomizeAllChartAreas();
53      chart.ChartAreas[0].CursorX.Interval = 1;
54    }
55
56    #region Event Handler Registration
57    protected override void DeregisterContentEvents() {
58      foreach (ScatterPlotDataRow row in Content.Rows)
59        DeregisterScatterPlotDataRowEvents(row);
60      Content.VisualPropertiesChanged -= new EventHandler(Content_VisualPropertiesChanged);
61      Content.Rows.ItemsAdded -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsAdded);
62      Content.Rows.ItemsRemoved -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsRemoved);
63      Content.Rows.ItemsReplaced -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsReplaced);
64      Content.Rows.CollectionReset -= new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_CollectionReset);
65      base.DeregisterContentEvents();
66    }
67    protected override void RegisterContentEvents() {
68      base.RegisterContentEvents();
69      Content.VisualPropertiesChanged += new EventHandler(Content_VisualPropertiesChanged);
70      Content.Rows.ItemsAdded += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsAdded);
71      Content.Rows.ItemsRemoved += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsRemoved);
72      Content.Rows.ItemsReplaced += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsReplaced);
73      Content.Rows.CollectionReset += new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_CollectionReset);
74    }
75
76    protected virtual void RegisterScatterPlotDataRowEvents(ScatterPlotDataRow row) {
77      row.NameChanged += new EventHandler(Row_NameChanged);
78      row.VisualPropertiesChanged += new EventHandler(Row_VisualPropertiesChanged);
79      pointsRowsTable.Add(row.Points, row);
80      row.Points.ItemsAdded += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded);
81      row.Points.ItemsRemoved += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved);
82      row.Points.ItemsReplaced += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced);
83      row.Points.CollectionReset += new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset);
84    }
85    protected virtual void DeregisterScatterPlotDataRowEvents(ScatterPlotDataRow row) {
86      row.Points.ItemsAdded -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded);
87      row.Points.ItemsRemoved -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved);
88      row.Points.ItemsReplaced -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced);
89      row.Points.CollectionReset -= new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset);
90      pointsRowsTable.Remove(row.Points);
91      row.VisualPropertiesChanged -= new EventHandler(Row_VisualPropertiesChanged);
92      row.NameChanged -= new EventHandler(Row_NameChanged);
93    }
94    #endregion
95
96    protected override void OnContentChanged() {
97      base.OnContentChanged();
98      invisibleSeries.Clear();
99      chart.ChartAreas[0].AxisX.Title = string.Empty;
100      chart.ChartAreas[0].AxisY.Title = string.Empty;
101      chart.Series.Clear();
102      if (Content != null) {
103        AddScatterPlotDataRows(Content.Rows);
104        ConfigureChartArea(chart.ChartAreas[0]);
105        RecalculateAxesScale(chart.ChartAreas[0]);
106      }
107    }
108
109    protected override void SetEnabledStateOfControls() {
110      base.SetEnabledStateOfControls();
111      chart.Enabled = Content != null;
112    }
113
114    public void ShowConfiguration() {
115      if (Content != null) {
116        using (ScatterPlotVisualPropertiesDialog dialog = new ScatterPlotVisualPropertiesDialog(Content)) {
117          dialog.ShowDialog(this);
118        }
119      } else MessageBox.Show("Nothing to configure.");
120    }
121
122    protected virtual void AddScatterPlotDataRows(IEnumerable<ScatterPlotDataRow> rows) {
123      foreach (var row in rows) {
124        RegisterScatterPlotDataRowEvents(row);
125        Series series = new Series(row.Name);
126        if (row.VisualProperties.DisplayName.Trim() != String.Empty) series.LegendText = row.VisualProperties.DisplayName;
127        else series.LegendText = row.Name;
128        ConfigureSeries(series, row);
129        FillSeriesWithRowValues(series, row);
130        chart.Series.Add(series);
131      }
132      ConfigureChartArea(chart.ChartAreas[0]);
133      RecalculateAxesScale(chart.ChartAreas[0]);
134      UpdateYCursorInterval();
135    }
136
137    protected virtual void RemoveScatterPlotDataRows(IEnumerable<ScatterPlotDataRow> rows) {
138      foreach (var row in rows) {
139        DeregisterScatterPlotDataRowEvents(row);
140        Series series = chart.Series[row.Name];
141        chart.Series.Remove(series);
142        if (invisibleSeries.Contains(series))
143          invisibleSeries.Remove(series);
144      }
145      RecalculateAxesScale(chart.ChartAreas[0]);
146    }
147
148    private void ConfigureSeries(Series series, ScatterPlotDataRow row) {
149      series.BorderWidth = 1;
150      series.BorderDashStyle = ChartDashStyle.Solid;
151      series.BorderColor = Color.Empty;
152
153      if (row.VisualProperties.Color != Color.Empty)
154        series.Color = row.VisualProperties.Color;
155      else series.Color = Color.Empty;
156      series.IsVisibleInLegend = row.VisualProperties.IsVisibleInLegend;
157      series.ChartType = SeriesChartType.FastPoint;
158      series.MarkerSize = row.VisualProperties.PointSize;
159      series.MarkerStyle = ConvertPointStyle(row.VisualProperties.PointStyle);
160      series.XAxisType = AxisType.Primary;
161      series.YAxisType = AxisType.Primary;
162
163      if (row.VisualProperties.DisplayName.Trim() != String.Empty) series.LegendText = row.VisualProperties.DisplayName;
164      else series.LegendText = row.Name;
165
166      string xAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.XAxisTitle)
167                      ? "X"
168                      : Content.VisualProperties.XAxisTitle;
169      string yAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.YAxisTitle)
170                            ? "Y"
171                            : Content.VisualProperties.YAxisTitle;
172      series.ToolTip =
173        series.LegendText + Environment.NewLine +
174        xAxisTitle + " = " + "#VALX," + Environment.NewLine +
175        yAxisTitle + " = " + "#VAL";
176    }
177
178    private void ConfigureChartArea(ChartArea area) {
179
180      if (Content.VisualProperties.AxisTitleFont != null) area.AxisX.TitleFont = Content.VisualProperties.AxisTitleFont;
181      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisX.TitleForeColor = Content.VisualProperties.AxisTitleColor;
182      area.AxisX.Title = Content.VisualProperties.XAxisTitle;
183      area.AxisX.MajorGrid.Enabled = Content.VisualProperties.XAxisGrid;
184
185      if (Content.VisualProperties.AxisTitleFont != null) area.AxisY.TitleFont = Content.VisualProperties.AxisTitleFont;
186      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisY.TitleForeColor = Content.VisualProperties.AxisTitleColor;
187      area.AxisY.Title = Content.VisualProperties.YAxisTitle;
188      area.AxisY.MajorGrid.Enabled = Content.VisualProperties.YAxisGrid;
189    }
190
191    private void RecalculateAxesScale(ChartArea area) {
192      // Reset the axes bounds so that RecalculateAxesScale() will assign new bounds
193      foreach (Axis a in area.Axes) {
194        a.Minimum = double.NaN;
195        a.Maximum = double.NaN;
196      }
197      area.RecalculateAxesScale();
198      area.AxisX.IsMarginVisible = false;
199
200      if (!Content.VisualProperties.XAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.XAxisMinimumFixedValue)) area.AxisX.Minimum = Content.VisualProperties.XAxisMinimumFixedValue;
201      if (!Content.VisualProperties.XAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.XAxisMaximumFixedValue)) area.AxisX.Maximum = Content.VisualProperties.XAxisMaximumFixedValue;
202      if (!Content.VisualProperties.YAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.YAxisMinimumFixedValue)) area.AxisY.Minimum = Content.VisualProperties.YAxisMinimumFixedValue;
203      if (!Content.VisualProperties.YAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.YAxisMaximumFixedValue)) area.AxisY.Maximum = Content.VisualProperties.YAxisMaximumFixedValue;
204      if (area.AxisX.Minimum >= area.AxisX.Maximum) area.AxisX.Maximum = area.AxisX.Minimum + 1;
205      if (area.AxisY.Minimum >= area.AxisY.Maximum) area.AxisY.Maximum = area.AxisY.Minimum + 1;
206    }
207
208    protected virtual void UpdateYCursorInterval() {
209      double interestingValuesRange = (
210        from series in chart.Series
211        where series.Enabled
212        let values = (from point in series.Points
213                      where !point.IsEmpty
214                      select point.YValues[0]).DefaultIfEmpty(1.0)
215        let range = values.Max() - values.Min()
216        where range > 0.0
217        select range
218        ).DefaultIfEmpty(1.0).Min();
219
220      double digits = (int)Math.Log10(interestingValuesRange) - 3;
221      double yZoomInterval = Math.Pow(10, digits);
222      this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval;
223    }
224
225
226    public event EventHandler ChartDoubleClick {
227      add { chartDoubleClick += value; }
228      remove { chartDoubleClick -= value; }
229    }
230
231    #region Event Handlers
232    #region Content Event Handlers
233
234    private void Content_VisualPropertiesChanged(object sender, EventArgs e) {
235      if (InvokeRequired)
236        Invoke(new EventHandler(Content_VisualPropertiesChanged), sender, e);
237      else {
238        ConfigureChartArea(chart.ChartAreas[0]);
239        RecalculateAxesScale(chart.ChartAreas[0]); // axes min/max could have changed
240      }
241    }
242    #endregion
243    #region Rows Event Handlers
244    private void Rows_ItemsAdded(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
245      if (InvokeRequired)
246        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsAdded), sender, e);
247      else {
248        AddScatterPlotDataRows(e.Items);
249      }
250    }
251    private void Rows_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
252      if (InvokeRequired)
253        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsRemoved), sender, e);
254      else {
255        RemoveScatterPlotDataRows(e.Items);
256      }
257    }
258    private void Rows_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
259      if (InvokeRequired)
260        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_ItemsReplaced), sender, e);
261      else {
262        RemoveScatterPlotDataRows(e.OldItems);
263        AddScatterPlotDataRows(e.Items);
264      }
265    }
266    private void Rows_CollectionReset(object sender, CollectionItemsChangedEventArgs<ScatterPlotDataRow> e) {
267      if (InvokeRequired)
268        Invoke(new CollectionItemsChangedEventHandler<ScatterPlotDataRow>(Rows_CollectionReset), sender, e);
269      else {
270        RemoveScatterPlotDataRows(e.OldItems);
271        AddScatterPlotDataRows(e.Items);
272      }
273    }
274    #endregion
275    #region Row Event Handlers
276    private void Row_VisualPropertiesChanged(object sender, EventArgs e) {
277      if (InvokeRequired)
278        Invoke(new EventHandler(Row_VisualPropertiesChanged), sender, e);
279      else {
280        ScatterPlotDataRow row = (ScatterPlotDataRow)sender;
281        Series series = chart.Series[row.Name];
282        series.Points.Clear();
283        ConfigureSeries(series, row);
284        FillSeriesWithRowValues(series, row);
285        RecalculateAxesScale(chart.ChartAreas[0]);
286      }
287    }
288    private void Row_NameChanged(object sender, EventArgs e) {
289      if (InvokeRequired)
290        Invoke(new EventHandler(Row_NameChanged), sender, e);
291      else {
292        ScatterPlotDataRow row = (ScatterPlotDataRow)sender;
293        chart.Series[row.Name].Name = row.Name;
294      }
295    }
296    #endregion
297    #region Points Event Handlers
298    private void Points_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
299      if (InvokeRequired)
300        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsAdded), sender, e);
301      else {
302        ScatterPlotDataRow row = null;
303        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
304        if (row != null) {
305          Series rowSeries = chart.Series[row.Name];
306          if (!invisibleSeries.Contains(rowSeries)) {
307            rowSeries.Points.Clear();
308            FillSeriesWithRowValues(rowSeries, row);
309            RecalculateAxesScale(chart.ChartAreas[0]);
310            UpdateYCursorInterval();
311          }
312        }
313      }
314    }
315    private void Points_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
316      if (InvokeRequired)
317        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsRemoved), sender, e);
318      else {
319        ScatterPlotDataRow row = null;
320        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
321        if (row != null) {
322          Series rowSeries = chart.Series[row.Name];
323          if (!invisibleSeries.Contains(rowSeries)) {
324            rowSeries.Points.Clear();
325            FillSeriesWithRowValues(rowSeries, row);
326            RecalculateAxesScale(chart.ChartAreas[0]);
327            UpdateYCursorInterval();
328          }
329        }
330      }
331    }
332    private void Points_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
333      if (InvokeRequired)
334        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_ItemsReplaced), sender, e);
335      else {
336        ScatterPlotDataRow row = null;
337        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
338        if (row != null) {
339          Series rowSeries = chart.Series[row.Name];
340          if (!invisibleSeries.Contains(rowSeries)) {
341            rowSeries.Points.Clear();
342            FillSeriesWithRowValues(rowSeries, row);
343            RecalculateAxesScale(chart.ChartAreas[0]);
344            UpdateYCursorInterval();
345          }
346        }
347      }
348    }
349    private void Points_CollectionReset(object sender, CollectionItemsChangedEventArgs<IndexedItem<Point2D<double>>> e) {
350      if (InvokeRequired)
351        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Point2D<double>>>(Points_CollectionReset), sender, e);
352      else {
353        ScatterPlotDataRow row = null;
354        pointsRowsTable.TryGetValue((IObservableList<Point2D<double>>)sender, out row);
355        if (row != null) {
356          Series rowSeries = chart.Series[row.Name];
357          if (!invisibleSeries.Contains(rowSeries)) {
358            rowSeries.Points.Clear();
359            FillSeriesWithRowValues(rowSeries, row);
360            RecalculateAxesScale(chart.ChartAreas[0]);
361            UpdateYCursorInterval();
362          }
363        }
364      }
365    }
366    #endregion
367    #endregion
368
369    #region Chart Event Handlers
370    private void chart_MouseDown(object sender, MouseEventArgs e) {
371      HitTestResult result = chart.HitTest(e.X, e.Y);
372      if (result.ChartElementType == ChartElementType.LegendItem) {
373        ToggleSeriesVisible(result.Series);
374      }
375    }
376    private void chart_MouseMove(object sender, MouseEventArgs e) {
377      HitTestResult result = chart.HitTest(e.X, e.Y);
378      if (result.ChartElementType == ChartElementType.LegendItem)
379        this.Cursor = Cursors.Hand;
380      else
381        this.Cursor = Cursors.Default;
382    }
383    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
384      foreach (LegendItem legendItem in e.LegendItems) {
385        var series = chart.Series[legendItem.SeriesName];
386        if (series != null) {
387          bool seriesIsInvisible = invisibleSeries.Contains(series);
388          foreach (LegendCell cell in legendItem.Cells) {
389            cell.ForeColor = seriesIsInvisible ? Color.Gray : Color.Black;
390          }
391        }
392      }
393    }
394    #endregion
395
396    private void ToggleSeriesVisible(Series series) {
397      if (!invisibleSeries.Contains(series)) {
398        series.Points.Clear();
399        invisibleSeries.Add(series);
400      } else {
401        invisibleSeries.Remove(series);
402        if (Content != null) {
403
404          var row = (from r in Content.Rows
405                     where r.Name == series.Name
406                     select r).Single();
407          FillSeriesWithRowValues(series, row);
408          this.chart.Legends[series.Legend].ForeColor = Color.Black;
409          RecalculateAxesScale(chart.ChartAreas[0]);
410          UpdateYCursorInterval();
411        }
412      }
413    }
414
415    private void FillSeriesWithRowValues(Series series, ScatterPlotDataRow row) {
416      for (int i = 0; i < row.Points.Count; i++) {
417        var value = row.Points[i];
418        DataPoint point = new DataPoint();
419        if (IsInvalidValue(value.X) || IsInvalidValue(value.Y))
420          point.IsEmpty = true;
421        else {
422          point.XValue = value.X;
423          point.YValues = new double[] { value.Y };
424        }
425        series.Points.Add(point);
426      }
427    }
428
429    #region Helpers
430    private MarkerStyle ConvertPointStyle(ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle pointStyle) {
431      switch (pointStyle) {
432        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Circle:
433          return MarkerStyle.Circle;
434        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Cross:
435          return MarkerStyle.Cross;
436        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Diamond:
437          return MarkerStyle.Diamond;
438        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Square:
439          return MarkerStyle.Square;
440        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star4:
441          return MarkerStyle.Star4;
442        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star5:
443          return MarkerStyle.Star5;
444        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star6:
445          return MarkerStyle.Star6;
446        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Star10:
447          return MarkerStyle.Star10;
448        case ScatterPlotDataRowVisualProperties.ScatterPlotDataRowPointStyle.Triangle:
449          return MarkerStyle.Triangle;
450        default:
451          return MarkerStyle.None;
452      }
453    }
454
455    protected static bool IsInvalidValue(double x) {
456      return double.IsNaN(x) || x < (double)decimal.MinValue || x > (double)decimal.MaxValue;
457    }
458    #endregion
459
460    //bubble double click event with scatter plot view as sender
461    private void chart_DoubleClick(object sender, EventArgs e) {
462      if (chartDoubleClick != null)
463        chartDoubleClick(this, e);
464    }
465  }
466}
Note: See TracBrowser for help on using the repository browser.