Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Analysis.Views/3.3/DataTableView.cs @ 15068

Last change on this file since 15068 was 15068, checked in by pfleck, 7 years ago

#2715 Moved the histogram properties (nr of bins, exact/approximate bins, aggregation) from DataRowVisualProperties to DataTableVisualProperties

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