Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 13321 was 12718, checked in by mkommend, 9 years ago

#2335: Merged all changes into stable.

File size: 37.2 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.Core.Views;
30using HeuristicLab.DataPreprocessing.Implementations;
31using HeuristicLab.MainForm;
32using HeuristicLab.Analysis;
33using HeuristicLab.Analysis.Views;
34
35namespace HeuristicLab.DataPreprocessing.Views {
36  [View("Preprocessing DataTable View")]
37  [Content(typeof(PreprocessingDataTable), false)]
38  public partial class PreprocessingDataTableView : ItemView, IConfigureableView {
39    protected List<Series> invisibleSeries;
40    protected Dictionary<IObservableList<double>, DataRow> valuesRowsTable;
41    private event EventHandler chartDoubleClick;
42
43    public new PreprocessingDataTable Content {
44      get { return (PreprocessingDataTable)base.Content; }
45      set { base.Content = value; }
46    }
47
48    public IEnumerable<double> Classification { get; set; }
49    public bool IsDetailedChartViewEnabled { get; set; }
50
51    public PreprocessingDataTableView() {
52      InitializeComponent();
53      valuesRowsTable = new Dictionary<IObservableList<double>, DataRow>();
54      invisibleSeries = new List<Series>();
55      chart.CustomizeAllChartAreas();
56      chart.ChartAreas[0].CursorX.Interval = 1;
57    }
58
59    #region Event Handler Registration
60    protected override void DeregisterContentEvents() {
61      foreach (DataRow row in Content.Rows)
62        DeregisterDataRowEvents(row);
63      Content.VisualPropertiesChanged -= new EventHandler(Content_VisualPropertiesChanged);
64      Content.Rows.ItemsAdded -= new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsAdded);
65      Content.Rows.ItemsRemoved -= new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsRemoved);
66      Content.Rows.ItemsReplaced -= new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsReplaced);
67      Content.Rows.CollectionReset -= new CollectionItemsChangedEventHandler<DataRow>(Rows_CollectionReset);
68
69      Content.SelectedRows.ItemsAdded -= new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsAdded);
70      Content.SelectedRows.ItemsRemoved -= new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsRemoved);
71      Content.SelectedRows.ItemsReplaced -= new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsReplaced);
72      Content.SelectedRows.CollectionReset -= new CollectionItemsChangedEventHandler<DataRow>(Rows_CollectionReset);
73      base.DeregisterContentEvents();
74    }
75    protected override void RegisterContentEvents() {
76      base.RegisterContentEvents();
77      Content.VisualPropertiesChanged += new EventHandler(Content_VisualPropertiesChanged);
78      Content.Rows.ItemsAdded += new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsAdded);
79      Content.Rows.ItemsRemoved += new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsRemoved);
80      Content.Rows.ItemsReplaced += new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsReplaced);
81      Content.Rows.CollectionReset += new CollectionItemsChangedEventHandler<DataRow>(Rows_CollectionReset);
82
83      Content.SelectedRows.ItemsAdded += new CollectionItemsChangedEventHandler<DataRow>(SelectedRows_ItemsAdded);
84      Content.SelectedRows.ItemsRemoved += new CollectionItemsChangedEventHandler<DataRow>(SelectedRows_ItemsRemoved);
85      Content.SelectedRows.ItemsReplaced += new CollectionItemsChangedEventHandler<DataRow>(SelectedRows_ItemsReplaced);
86      Content.SelectedRows.CollectionReset += new CollectionItemsChangedEventHandler<DataRow>(SelectedRows_CollectionReset);
87    }
88
89
90    protected virtual void RegisterDataRowEvents(DataRow row) {
91      row.NameChanged += new EventHandler(Row_NameChanged);
92      row.VisualPropertiesChanged += new EventHandler(Row_VisualPropertiesChanged);
93      valuesRowsTable.Add(row.Values, row);
94      row.Values.ItemsAdded += new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsAdded);
95      row.Values.ItemsRemoved += new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsRemoved);
96      row.Values.ItemsReplaced += new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsReplaced);
97      row.Values.ItemsMoved += new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsMoved);
98      row.Values.CollectionReset += new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_CollectionReset);
99    }
100    protected virtual void DeregisterDataRowEvents(DataRow row) {
101      row.Values.ItemsAdded -= new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsAdded);
102      row.Values.ItemsRemoved -= new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsRemoved);
103      row.Values.ItemsReplaced -= new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsReplaced);
104      row.Values.ItemsMoved -= new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsMoved);
105      row.Values.CollectionReset -= new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_CollectionReset);
106      valuesRowsTable.Remove(row.Values);
107      row.VisualPropertiesChanged -= new EventHandler(Row_VisualPropertiesChanged);
108      row.NameChanged -= new EventHandler(Row_NameChanged);
109    }
110    #endregion
111
112    protected override void OnContentChanged() {
113      base.OnContentChanged();
114      invisibleSeries.Clear();
115      chart.ChartAreas[0].AxisX.Title = string.Empty;
116      chart.ChartAreas[0].AxisY.Title = string.Empty;
117      chart.ChartAreas[0].AxisY2.Title = string.Empty;
118      chart.Series.Clear();
119      if (Content != null) {
120
121        if (Classification != null)
122          chart.Titles[0].Text = Content.Name;
123
124        AddDataRows(Content.Rows);
125        AddSelectedDataRows(Content.SelectedRows);
126        ConfigureChartArea(chart.ChartAreas[0]);
127        RecalculateAxesScale(chart.ChartAreas[0]);
128      }
129    }
130
131    protected override void SetEnabledStateOfControls() {
132      base.SetEnabledStateOfControls();
133      chart.Enabled = Content != null;
134    }
135
136    public void ShowConfiguration() {
137      if (Content != null) {
138        using (var dialog = new DataTableVisualPropertiesDialog(Content)) {
139          dialog.ShowDialog(this);
140        }
141      } else MessageBox.Show("Nothing to configure.");
142    }
143
144    protected virtual void AddDataRows(IEnumerable<DataRow> rows) {
145      foreach (var row in rows) {
146        RegisterDataRowEvents(row);
147        var series = new Series(row.Name);
148        if (row.VisualProperties.DisplayName.Trim() != String.Empty)
149          series.LegendText = row.VisualProperties.DisplayName;
150        else series.LegendText = row.Name;
151
152        ConfigureSeries(series, row);
153        FillSeriesWithRowValues(series, row);
154
155        if (IsDetailedChartViewEnabled) {
156          series.LegendText += " Values: " + row.Values.Count;
157        }
158        if (Classification == null)
159          chart.Series.Add(series);
160      }
161
162      ConfigureChartArea(chart.ChartAreas[0]);
163      RecalculateAxesScale(chart.ChartAreas[0]);
164      UpdateYCursorInterval();
165    }
166
167    protected virtual void AddSelectedDataRows(IEnumerable<DataRow> rows) {
168
169      // only paint selection for line chart
170      if (rows.Count() > 0 && rows.ElementAt(0).VisualProperties.ChartType == DataRowVisualProperties.DataRowChartType.Line) {
171        // add dummy series for selction entry in legend
172        if (rows.Count() > 0 && chart.Series.FindByName("(Selection)") == null) {
173          Series series = new Series("(Selection)");
174          series.IsVisibleInLegend = true;
175          series.Color = Color.Green;
176          series.BorderWidth = 1;
177          series.BorderDashStyle = ChartDashStyle.Solid;
178          series.BorderColor = Color.Empty;
179          series.ChartType = SeriesChartType.FastLine;
180          chart.Series.Add(series);
181        }
182
183        foreach (var row in rows) {
184          row.VisualProperties.IsVisibleInLegend = false;
185          row.VisualProperties.Color = Color.Green;
186          //add selected to name in order to avoid naming conflict
187          var series = new Series(row.Name + "(Selected)");
188          ConfigureSeries(series, row);
189          FillSeriesWithRowValues(series, row);
190
191          if (Classification == null)
192            chart.Series.Add(series);
193
194        }
195      }
196    }
197
198    protected virtual void RemoveSelectedDataRows(IEnumerable<DataRow> rows) {
199
200      // only remove selection for line chart
201      if (rows.Count() > 0 && rows.ElementAt(0).VisualProperties.ChartType == DataRowVisualProperties.DataRowChartType.Line) {
202        //remove selection entry in legend
203        if (Content.SelectedRows.Count == 0) {
204          Series series = chart.Series["(Selection)"];
205          chart.Series.Remove(series);
206        }
207
208        foreach (var row in rows) {
209          Series series = chart.Series[row.Name + "(Selected)"];
210          chart.Series.Remove(series);
211        }
212      }
213    }
214
215    protected virtual void RemoveDataRows(IEnumerable<DataRow> rows) {
216      foreach (var row in rows) {
217        DeregisterDataRowEvents(row);
218        Series series = chart.Series[row.Name];
219        chart.Series.Remove(series);
220        if (invisibleSeries.Contains(series))
221          invisibleSeries.Remove(series);
222      }
223      RecalculateAxesScale(chart.ChartAreas[0]);
224    }
225
226    private void ConfigureSeries(Series series, DataRow row) {
227      RemoveCustomPropertyIfExists(series, "PointWidth");
228      series.BorderWidth = 1;
229      series.BorderDashStyle = ChartDashStyle.Solid;
230      series.BorderColor = Color.Empty;
231
232      if (row.VisualProperties.Color != Color.Empty)
233        series.Color = row.VisualProperties.Color;
234      else series.Color = Color.Empty;
235      series.IsVisibleInLegend = row.VisualProperties.IsVisibleInLegend;
236
237      switch (row.VisualProperties.ChartType) {
238        case DataRowVisualProperties.DataRowChartType.Line:
239          series.ChartType = SeriesChartType.FastLine;
240          series.BorderWidth = row.VisualProperties.LineWidth;
241          series.BorderDashStyle = ConvertLineStyle(row.VisualProperties.LineStyle);
242          break;
243        case DataRowVisualProperties.DataRowChartType.Bars:
244          // Bar is incompatible with anything but Bar and StackedBar*
245          if (!chart.Series.Any(x => x.ChartType != SeriesChartType.Bar && x.ChartType != SeriesChartType.StackedBar && x.ChartType != SeriesChartType.StackedBar100))
246            series.ChartType = SeriesChartType.Bar;
247          else {
248            series.ChartType = SeriesChartType.FastPoint; //default
249            row.VisualProperties.ChartType = DataRowVisualProperties.DataRowChartType.Points;
250          }
251          break;
252        case DataRowVisualProperties.DataRowChartType.Columns:
253          series.ChartType = SeriesChartType.Column;
254          break;
255        case DataRowVisualProperties.DataRowChartType.Points:
256          series.ChartType = SeriesChartType.FastPoint;
257          break;
258        case DataRowVisualProperties.DataRowChartType.Histogram:
259          series.ChartType = SeriesChartType.StackedColumn;
260          series.SetCustomProperty("PointWidth", "1");
261          if (!series.Color.IsEmpty && series.Color.GetBrightness() < 0.25)
262            series.BorderColor = Color.White;
263          else series.BorderColor = Color.Black;
264          break;
265        case DataRowVisualProperties.DataRowChartType.StepLine:
266          series.ChartType = SeriesChartType.StepLine;
267          series.BorderWidth = row.VisualProperties.LineWidth;
268          series.BorderDashStyle = ConvertLineStyle(row.VisualProperties.LineStyle);
269          break;
270        default:
271          series.ChartType = SeriesChartType.FastPoint;
272          break;
273      }
274      series.YAxisType = row.VisualProperties.SecondYAxis ? AxisType.Secondary : AxisType.Primary;
275      series.XAxisType = row.VisualProperties.SecondXAxis ? AxisType.Secondary : AxisType.Primary;
276      if (row.VisualProperties.DisplayName.Trim() != String.Empty) series.LegendText = row.VisualProperties.DisplayName;
277      else series.LegendText = row.Name;
278
279      string xAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.XAxisTitle)
280                      ? "X"
281                      : Content.VisualProperties.XAxisTitle;
282      string yAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.YAxisTitle)
283                            ? "Y"
284                            : Content.VisualProperties.YAxisTitle;
285      series.ToolTip =
286        series.LegendText + Environment.NewLine +
287        xAxisTitle + " = " + "#INDEX," + Environment.NewLine +
288        yAxisTitle + " = " + "#VAL";
289    }
290
291    private void ConfigureChartArea(ChartArea area) {
292      if (Content.VisualProperties.TitleFont != null) chart.Titles[0].Font = Content.VisualProperties.TitleFont;
293      if (!Content.VisualProperties.TitleColor.IsEmpty) chart.Titles[0].ForeColor = Content.VisualProperties.TitleColor;
294      chart.Titles[0].Text = Content.VisualProperties.Title;
295
296      if (Content.VisualProperties.AxisTitleFont != null) area.AxisX.TitleFont = Content.VisualProperties.AxisTitleFont;
297      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisX.TitleForeColor = Content.VisualProperties.AxisTitleColor;
298      area.AxisX.Title = Content.VisualProperties.XAxisTitle;
299
300      if (Content.VisualProperties.AxisTitleFont != null) area.AxisX2.TitleFont = Content.VisualProperties.AxisTitleFont;
301      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisX2.TitleForeColor = Content.VisualProperties.AxisTitleColor;
302      area.AxisX2.Title = Content.VisualProperties.SecondXAxisTitle;
303
304      if (Content.VisualProperties.AxisTitleFont != null) area.AxisY.TitleFont = Content.VisualProperties.AxisTitleFont;
305      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisY.TitleForeColor = Content.VisualProperties.AxisTitleColor;
306      area.AxisY.Title = Content.VisualProperties.YAxisTitle;
307
308      if (Content.VisualProperties.AxisTitleFont != null) area.AxisY2.TitleFont = Content.VisualProperties.AxisTitleFont;
309      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisY2.TitleForeColor = Content.VisualProperties.AxisTitleColor;
310      area.AxisY2.Title = Content.VisualProperties.SecondYAxisTitle;
311
312      area.AxisX.IsLogarithmic = Content.VisualProperties.XAxisLogScale;
313      area.AxisX2.IsLogarithmic = Content.VisualProperties.SecondXAxisLogScale;
314      area.AxisY.IsLogarithmic = Content.VisualProperties.YAxisLogScale;
315      area.AxisY2.IsLogarithmic = Content.VisualProperties.SecondYAxisLogScale;
316    }
317
318    private void RecalculateAxesScale(ChartArea area) {
319      // Reset the axes bounds so that RecalculateAxesScale() will assign new bounds
320      foreach (Axis a in area.Axes) {
321        a.Minimum = double.NaN;
322        a.Maximum = double.NaN;
323      }
324      area.RecalculateAxesScale();
325      area.AxisX.IsMarginVisible = false;
326      area.AxisX2.IsMarginVisible = false;
327
328      if (!Content.VisualProperties.XAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.XAxisMinimumFixedValue)) area.AxisX.Minimum = Content.VisualProperties.XAxisMinimumFixedValue;
329      if (!Content.VisualProperties.XAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.XAxisMaximumFixedValue)) area.AxisX.Maximum = Content.VisualProperties.XAxisMaximumFixedValue;
330      if (!Content.VisualProperties.SecondXAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.SecondXAxisMinimumFixedValue)) area.AxisX2.Minimum = Content.VisualProperties.SecondXAxisMinimumFixedValue;
331      if (!Content.VisualProperties.SecondXAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.SecondXAxisMaximumFixedValue)) area.AxisX2.Maximum = Content.VisualProperties.SecondXAxisMaximumFixedValue;
332      if (!Content.VisualProperties.YAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.YAxisMinimumFixedValue)) area.AxisY.Minimum = Content.VisualProperties.YAxisMinimumFixedValue;
333      if (!Content.VisualProperties.YAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.YAxisMaximumFixedValue)) area.AxisY.Maximum = Content.VisualProperties.YAxisMaximumFixedValue;
334      if (!Content.VisualProperties.SecondYAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.SecondYAxisMinimumFixedValue)) area.AxisY2.Minimum = Content.VisualProperties.SecondYAxisMinimumFixedValue;
335      if (!Content.VisualProperties.SecondYAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.SecondYAxisMaximumFixedValue)) area.AxisY2.Maximum = Content.VisualProperties.SecondYAxisMaximumFixedValue;
336      if (area.AxisX.Minimum >= area.AxisX.Maximum) area.AxisX.Maximum = area.AxisX.Minimum + 1;
337      if (area.AxisX2.Minimum >= area.AxisX2.Maximum) area.AxisX2.Maximum = area.AxisX2.Minimum + 1;
338      if (area.AxisY.Minimum >= area.AxisY.Maximum) area.AxisY.Maximum = area.AxisY.Minimum + 1;
339      if (area.AxisY2.Minimum >= area.AxisY2.Maximum) area.AxisY2.Maximum = area.AxisY2.Minimum + 1;
340    }
341
342    protected virtual void UpdateYCursorInterval() {
343      double interestingValuesRange = (
344        from series in chart.Series
345        where series.Enabled
346        let values = (from point in series.Points
347                      where !point.IsEmpty
348                      select point.YValues[0]).DefaultIfEmpty(1.0)
349        let range = values.Max() - values.Min()
350        where range > 0.0
351        select range
352        ).DefaultIfEmpty(1.0).Min();
353
354      double digits = (int)Math.Log10(interestingValuesRange) - 3;
355      double yZoomInterval = Math.Pow(10, digits);
356      this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval;
357    }
358
359    #region Event Handlers
360    #region Content Event Handlers
361    private void Content_VisualPropertiesChanged(object sender, EventArgs e) {
362      if (InvokeRequired)
363        Invoke(new EventHandler(Content_VisualPropertiesChanged), sender, e);
364      else {
365        ConfigureChartArea(chart.ChartAreas[0]);
366        RecalculateAxesScale(chart.ChartAreas[0]); // axes min/max could have changed
367      }
368    }
369    #endregion
370    #region Rows Event Handlers
371
372    private void SelectedRows_CollectionReset(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
373      if (InvokeRequired)
374        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsReplaced), sender, e);
375      else {
376        RemoveSelectedDataRows(e.OldItems);
377        AddSelectedDataRows(e.Items);
378      }
379    }
380
381    private void SelectedRows_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
382      if (InvokeRequired)
383        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsReplaced), sender, e);
384      else {
385        RemoveSelectedDataRows(e.OldItems);
386        AddSelectedDataRows(e.Items);
387      }
388    }
389
390    private void SelectedRows_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
391      if (InvokeRequired)
392        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsRemoved), sender, e);
393      else {
394        RemoveSelectedDataRows(e.Items);
395      }
396    }
397
398    private void SelectedRows_ItemsAdded(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
399      if (InvokeRequired)
400        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsAdded), sender, e);
401      else {
402        AddSelectedDataRows(e.Items);
403      }
404    }
405
406
407    private void Rows_ItemsAdded(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
408      if (InvokeRequired)
409        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsAdded), sender, e);
410      else {
411        AddDataRows(e.Items);
412      }
413    }
414    private void Rows_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
415      if (InvokeRequired)
416        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsRemoved), sender, e);
417      else {
418        RemoveDataRows(e.Items);
419      }
420    }
421    private void Rows_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
422      if (InvokeRequired)
423        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsReplaced), sender, e);
424      else {
425        RemoveDataRows(e.OldItems);
426        AddDataRows(e.Items);
427      }
428    }
429    private void Rows_CollectionReset(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
430      if (InvokeRequired)
431        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_CollectionReset), sender, e);
432      else {
433        RemoveDataRows(e.OldItems);
434        AddDataRows(e.Items);
435      }
436    }
437    #endregion
438    #region Row Event Handlers
439    private void Row_VisualPropertiesChanged(object sender, EventArgs e) {
440      if (InvokeRequired)
441        Invoke(new EventHandler(Row_VisualPropertiesChanged), sender, e);
442      else {
443        DataRow row = (DataRow)sender;
444        Series series = chart.Series[row.Name];
445        series.Points.Clear();
446        ConfigureSeries(series, row);
447        FillSeriesWithRowValues(series, row);
448        RecalculateAxesScale(chart.ChartAreas[0]);
449      }
450    }
451    private void Row_NameChanged(object sender, EventArgs e) {
452      if (InvokeRequired)
453        Invoke(new EventHandler(Row_NameChanged), sender, e);
454      else {
455        DataRow row = (DataRow)sender;
456        chart.Series[row.Name].Name = row.Name;
457      }
458    }
459    #endregion
460    #region Values Event Handlers
461    private void Values_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<double>> e) {
462      if (InvokeRequired)
463        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsAdded), sender, e);
464      else {
465        DataRow row = null;
466        valuesRowsTable.TryGetValue((IObservableList<double>)sender, out row);
467        if (row != null) {
468          Series rowSeries = chart.Series[row.Name];
469          if (!invisibleSeries.Contains(rowSeries)) {
470            rowSeries.Points.Clear();
471            FillSeriesWithRowValues(rowSeries, row);
472            RecalculateAxesScale(chart.ChartAreas[0]);
473            UpdateYCursorInterval();
474          }
475        }
476      }
477    }
478    private void Values_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<double>> e) {
479      if (InvokeRequired)
480        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsRemoved), sender, e);
481      else {
482        DataRow row = null;
483        valuesRowsTable.TryGetValue((IObservableList<double>)sender, out row);
484        if (row != null) {
485          Series rowSeries = chart.Series[row.Name];
486          if (!invisibleSeries.Contains(rowSeries)) {
487            rowSeries.Points.Clear();
488            FillSeriesWithRowValues(rowSeries, row);
489            RecalculateAxesScale(chart.ChartAreas[0]);
490            UpdateYCursorInterval();
491          }
492        }
493      }
494    }
495    private void Values_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<IndexedItem<double>> e) {
496      if (InvokeRequired)
497        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsReplaced), sender, e);
498      else {
499        DataRow row = null;
500        valuesRowsTable.TryGetValue((IObservableList<double>)sender, out row);
501        if (row != null) {
502          Series rowSeries = chart.Series[row.Name];
503          if (!invisibleSeries.Contains(rowSeries)) {
504            if (row.VisualProperties.ChartType == DataRowVisualProperties.DataRowChartType.Histogram) {
505              rowSeries.Points.Clear();
506              FillSeriesWithRowValues(rowSeries, row);
507            } else {
508              foreach (IndexedItem<double> item in e.Items) {
509                if (IsInvalidValue(item.Value))
510                  rowSeries.Points[item.Index].IsEmpty = true;
511                else {
512                  rowSeries.Points[item.Index].YValues = new double[] { item.Value };
513                  rowSeries.Points[item.Index].IsEmpty = false;
514                }
515              }
516            }
517            RecalculateAxesScale(chart.ChartAreas[0]);
518            UpdateYCursorInterval();
519          }
520        }
521      }
522    }
523    private void Values_ItemsMoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<double>> e) {
524      if (InvokeRequired)
525        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsMoved), sender, e);
526      else {
527        DataRow row = null;
528        valuesRowsTable.TryGetValue((IObservableList<double>)sender, out row);
529        if (row != null) {
530          Series rowSeries = chart.Series[row.Name];
531          if (!invisibleSeries.Contains(rowSeries)) {
532            rowSeries.Points.Clear();
533            FillSeriesWithRowValues(rowSeries, row);
534            RecalculateAxesScale(chart.ChartAreas[0]);
535            UpdateYCursorInterval();
536          }
537        }
538      }
539    }
540
541    private void Values_CollectionReset(object sender, CollectionItemsChangedEventArgs<IndexedItem<double>> e) {
542      if (InvokeRequired)
543        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_CollectionReset), sender, e);
544      else {
545        DataRow row = null;
546        valuesRowsTable.TryGetValue((IObservableList<double>)sender, out row);
547        if (row != null) {
548          Series rowSeries = chart.Series[row.Name];
549          if (!invisibleSeries.Contains(rowSeries)) {
550            rowSeries.Points.Clear();
551            FillSeriesWithRowValues(rowSeries, row);
552            RecalculateAxesScale(chart.ChartAreas[0]);
553            UpdateYCursorInterval();
554          }
555        }
556      }
557    }
558    #endregion
559    #endregion
560
561    #region Chart Event Handlers
562    private void chart_MouseDown(object sender, MouseEventArgs e) {
563      HitTestResult result = chart.HitTest(e.X, e.Y);
564      if (result.ChartElementType == ChartElementType.LegendItem) {
565        ToggleSeriesVisible(result.Series);
566      }
567    }
568    private void chart_MouseMove(object sender, MouseEventArgs e) {
569      HitTestResult result = chart.HitTest(e.X, e.Y);
570      if (result.ChartElementType == ChartElementType.LegendItem)
571        this.Cursor = Cursors.Hand;
572      else
573        this.Cursor = Cursors.Default;
574    }
575    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
576      foreach (LegendItem legendItem in e.LegendItems) {
577        var series = chart.Series[legendItem.SeriesName];
578        if (series != null) {
579          bool seriesIsInvisible = invisibleSeries.Contains(series);
580          foreach (LegendCell cell in legendItem.Cells) {
581            cell.ForeColor = seriesIsInvisible ? Color.Gray : Color.Black;
582          }
583        }
584      }
585    }
586    #endregion
587
588    private void ToggleSeriesVisible(Series series) {
589
590      if (!Content.Rows.Any(x => x.Name == series.Name))
591        return;
592
593      if (!invisibleSeries.Contains(series)) {
594        series.Points.Clear();
595        invisibleSeries.Add(series);
596      } else {
597        invisibleSeries.Remove(series);
598        if (Content != null) {
599
600          var row = (from r in Content.Rows
601                     where r.Name == series.Name
602                     select r).Single();
603          FillSeriesWithRowValues(series, row);
604          this.chart.Legends[series.Legend].ForeColor = Color.Black;
605          RecalculateAxesScale(chart.ChartAreas[0]);
606          UpdateYCursorInterval();
607        }
608      }
609    }
610
611    private void FillSeriesWithRowValues(Series series, DataRow row) {
612      switch (row.VisualProperties.ChartType) {
613        case DataRowVisualProperties.DataRowChartType.Histogram:
614          CalculateHistogram(series, row);
615          break;
616        default: {
617            bool yLogarithmic = series.YAxisType == AxisType.Primary
618                                  ? Content.VisualProperties.YAxisLogScale
619                                  : Content.VisualProperties.SecondYAxisLogScale;
620            bool xLogarithmic = series.XAxisType == AxisType.Primary
621                                  ? Content.VisualProperties.XAxisLogScale
622                                  : Content.VisualProperties.SecondXAxisLogScale;
623            for (int i = 0; i < row.Values.Count; i++) {
624              var value = row.Values[i];
625              var point = new DataPoint();
626              point.XValue = row.VisualProperties.StartIndexZero && !xLogarithmic ? i : i + 1;
627              if (IsInvalidValue(value) || (yLogarithmic && value <= 0))
628                point.IsEmpty = true;
629              else
630                point.YValues = new double[] { value };
631              series.Points.Add(point);
632            }
633          }
634          break;
635      }
636    }
637
638    // get minimum ignores nan values
639    private double GetMinimum(IEnumerable<double> values) {
640      double min = Double.MaxValue;
641
642      foreach (double value in values) {
643        if (!Double.IsNaN(value) && value < min)
644          min = value;
645      }
646      return min;
647    }
648
649    //get maximium ignores nan values
650    private double GetMaximum(IEnumerable<double> values) {
651      double max = Double.MinValue;
652
653      foreach (double value in values) {
654        if (!Double.IsNaN(value) && value > max)
655          max = value;
656      }
657      return max;
658    }
659
660    protected virtual void CalculateHistogram(Series series, DataRow row) {
661      if (Classification != null) {
662
663        var valuesPerClass = row.Values.Select((i, index) => new { i, j = Classification.ToList()[index] })
664                                       .GroupBy((x) => x.j)
665                                       .ToDictionary(x => x.Key, x => x.Select(v => v.i)
666                                       .ToList());
667
668        chart.Titles.Add(row.Name);
669        int featureOverallValueCount = 0;
670        if (IsDetailedChartViewEnabled)
671          featureOverallValueCount = row.Values.Count(x => !IsInvalidValue(x));
672        foreach (KeyValuePair<double, List<double>> entry in valuesPerClass) {
673          var s = new Series(row.Name + entry.Key);
674
675          ConfigureSeries(s, row);
676          AddPointsToHistogramSeries(s, row, entry.Value);
677
678          s.LegendText = entry.Key.ToString();
679          if (IsDetailedChartViewEnabled) {
680            int featureValueCount = entry.Value.Count(x => !IsInvalidValue(x));
681            s.LegendText += " Values: ";
682            s.LegendText += (featureOverallValueCount > 0) ?
683              string.Format("{0} ({1:F2}%)", featureValueCount, (featureValueCount / (double)featureOverallValueCount) * 100)
684            : "0";
685          }
686          chart.Series.Add(s);
687        }
688      } else {
689        series.Points.Clear();
690        ConfigureSeries(series, row);
691        AddPointsToHistogramSeries(series, row, null);
692      }
693    }
694
695    private void AddPointsToHistogramSeries(Series series, DataRow row, List<double> values) {
696      if (!row.Values.Any()) return;
697      int bins = row.VisualProperties.Bins;
698
699      double minValue = GetMinimum(row.Values);
700      double maxValue = GetMaximum(row.Values);
701      double intervalWidth = (maxValue - minValue) / bins;
702      if (intervalWidth < 0) return;
703      if (intervalWidth == 0) {
704        series.Points.AddXY(minValue, row.Values.Count);
705        return;
706      }
707
708
709      if (!row.VisualProperties.ExactBins) {
710        intervalWidth = HumanRoundRange(intervalWidth);
711        minValue = Math.Floor(minValue / intervalWidth) * intervalWidth;
712        maxValue = Math.Ceiling(maxValue / intervalWidth) * intervalWidth;
713      }
714
715      double intervalCenter = intervalWidth / 2;
716
717      double min = 0.0, max = 0.0;
718      if (!Double.IsNaN(Content.VisualProperties.XAxisMinimumFixedValue) && !Content.VisualProperties.XAxisMinimumAuto)
719        min = Content.VisualProperties.XAxisMinimumFixedValue;
720      else min = minValue;
721      if (!Double.IsNaN(Content.VisualProperties.XAxisMaximumFixedValue) && !Content.VisualProperties.XAxisMaximumAuto)
722        max = Content.VisualProperties.XAxisMaximumFixedValue;
723      else max = maxValue + intervalWidth;
724
725      double axisInterval = intervalWidth / row.VisualProperties.ScaleFactor;
726
727      var area = chart.ChartAreas[0];
728      area.AxisX.Interval = axisInterval;
729
730      series.SetCustomProperty("PointWidth", "1"); // 0.8 is the default value
731
732      // get the range or intervals which define the grouping of the frequency values
733      var doubleRange = DoubleRange(min, max, intervalWidth).Skip(1).ToList();
734
735
736      if (values == null) {
737        values = row.Values.ToList(); ;
738      }
739
740      // aggregate the row values by unique key and frequency value
741      var valueFrequencies = (from v in values
742                              where !IsInvalidValue(v)
743                              orderby v
744                              group v by v into g
745                              select new Tuple<double, double>(g.First(), g.Count())).ToList();
746
747      //  shift the chart to the left so the bars are placed on the intervals
748      if (Classification != null || valueFrequencies.First().Item1 < doubleRange.First()) {
749        series.Points.Add(new DataPoint(min - intervalWidth, 0));
750        series.Points.Add(new DataPoint(max + intervalWidth, 0));
751      }
752
753      // add data points
754      int j = 0;
755      foreach (var d in doubleRange) {
756        double sum = 0.0;
757        // sum the frequency values that fall within the same interval
758        while (j < valueFrequencies.Count && valueFrequencies[j].Item1 < d) {
759          sum += valueFrequencies[j].Item2;
760          ++j;
761        }
762        string xAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.XAxisTitle)
763                              ? "X"
764                              : Content.VisualProperties.XAxisTitle;
765        string yAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.YAxisTitle)
766                              ? "Y"
767                              : Content.VisualProperties.YAxisTitle;
768        DataPoint newDataPoint = new DataPoint(d - intervalCenter, sum);
769        newDataPoint.ToolTip =
770          xAxisTitle + ": [" + (d - intervalWidth) + "-" + d + ")" + Environment.NewLine +
771          yAxisTitle + ": " + sum;
772        int overallValueCount = row.Values.Count();
773        if (overallValueCount > 0)
774          newDataPoint.ToolTip += string.Format(" ({0:F2}%)", (sum / overallValueCount) * 100);
775        series.Points.Add(newDataPoint);
776      }
777    }
778
779    public event EventHandler ChartDoubleClick {
780      add { chartDoubleClick += value; }
781      remove { chartDoubleClick -= value; }
782    }
783
784    #region Helpers
785    public static IEnumerable<double> DoubleRange(double min, double max, double step) {
786      double i;
787      for (i = min; i <= max; i += step)
788        yield return i;
789
790      if (i != max + step)
791        yield return i;
792    }
793
794    protected void RemoveCustomPropertyIfExists(Series series, string property) {
795      if (series.IsCustomPropertySet(property)) series.DeleteCustomProperty(property);
796    }
797
798    private double HumanRoundRange(double range) {
799      double base10 = Math.Pow(10.0, Math.Floor(Math.Log10(range)));
800      double rounding = range / base10;
801      if (rounding <= 1.5) rounding = 1;
802      else if (rounding <= 2.25) rounding = 2;
803      else if (rounding <= 3.75) rounding = 2.5;
804      else if (rounding <= 7.5) rounding = 5;
805      else rounding = 10;
806      return rounding * base10;
807    }
808
809    private double HumanRoundMax(double max) {
810      double base10;
811      if (max > 0) base10 = Math.Pow(10.0, Math.Floor(Math.Log10(max)));
812      else base10 = Math.Pow(10.0, Math.Ceiling(Math.Log10(-max)));
813      double rounding = (max > 0) ? base10 : -base10;
814      while (rounding < max) rounding += base10;
815      return rounding;
816    }
817
818    private ChartDashStyle ConvertLineStyle(DataRowVisualProperties.DataRowLineStyle dataRowLineStyle) {
819      switch (dataRowLineStyle) {
820        case DataRowVisualProperties.DataRowLineStyle.Dash:
821          return ChartDashStyle.Dash;
822        case DataRowVisualProperties.DataRowLineStyle.DashDot:
823          return ChartDashStyle.DashDot;
824        case DataRowVisualProperties.DataRowLineStyle.DashDotDot:
825          return ChartDashStyle.DashDotDot;
826        case DataRowVisualProperties.DataRowLineStyle.Dot:
827          return ChartDashStyle.Dot;
828        case DataRowVisualProperties.DataRowLineStyle.NotSet:
829          return ChartDashStyle.NotSet;
830        case DataRowVisualProperties.DataRowLineStyle.Solid:
831          return ChartDashStyle.Solid;
832        default:
833          return ChartDashStyle.NotSet;
834      }
835    }
836
837    protected static bool IsInvalidValue(double x) {
838      return double.IsNaN(x) || x < (double)decimal.MinValue || x > (double)decimal.MaxValue;
839    }
840    #endregion
841
842    //bubble double click event with data table view as sender
843    private void chart_DoubleClick(object sender, EventArgs e) {
844      if (chartDoubleClick != null)
845        chartDoubleClick(this, e);
846    }
847  }
848}
Note: See TracBrowser for help on using the repository browser.