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

Last change on this file since 15097 was 15097, checked in by pfleck, 2 years ago

#2713 #2715 #2765
Merged to stable

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