Free cookie consent management tool by TermsFeed Policy Generator

source: branches/DataPreprocessing/HeuristicLab.DataPreprocessing.Views/3.3/PreprocessingDataTableView.cs @ 10851

Last change on this file since 10851 was 10851, checked in by aesterer, 8 years ago

Added selection tag in line chart legend

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