Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.Analysis.Views/3.3/ScatterPlotView.cs @ 12009

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

#2212 updated copyright year

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