Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 15104 was 14963, checked in by gkronber, 7 years ago

#2698: merged r14381 r14382 r14384 r14388 r14418 r14425 from trunk to stable

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