source: branches/symbreg-factors-2650/HeuristicLab.DataPreprocessing.Views/3.4/PreprocessingDataTableView.cs @ 14277

Last change on this file since 14277 was 14277, checked in by gkronber, 5 years ago

#2650: merged r14245:14273 from trunk to branch (fixing conflicts in RegressionSolutionTargetResponseGradientView)

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