source: trunk/sources/HeuristicLab.DataPreprocessing.Views/3.4/PreprocessingDataTableView.cs @ 13517

Last change on this file since 13517 was 13517, checked in by pfleck, 5 years ago

#2559

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