source: branches/symbreg-factors-2650/HeuristicLab.Analysis.Views/3.3/DataTableControl.cs @ 14753

Last change on this file since 14753 was 14753, checked in by gkronber, 6 months ago

#2650 merged r14740 from trunk to branch

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