Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 11166 was 10952, checked in by aesterer, 10 years ago

Completed scatter plot view

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