Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 14820 was 14381, checked in by pfleck, 8 years ago

#2698

  • Refactored CheckedVariablesView out of the ChartView to allow reuse of the checked variables list.
    • The new list visualizes the non-input/target variables in gray.
    • Added context menu to quickly (un)check all variables or only the inputs+target variables.
  • In the Multi-Scatterplot
    • New structure and layout of the single charts to support fixed header rows and columns (for the variable names). Instead, removed the legend of each plot for better usage of plot area.
    • Adapted the new CheckedVariablesView (but hidden until (un)checking is implemented).
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 = row.VisualProperties.Bins;
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 (!row.VisualProperties.ExactBins) {
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.