source: branches/2947_ConfigurableIndexedDataTable/HeuristicLab.Analysis.Views/3.3/IndexedDataTableView.cs @ 16150

Last change on this file since 16150 was 16150, checked in by pfleck, 2 years ago

#2947

  • Added context menu to open the VisualProperties dialog for the IndexedDataTableView.
  • Changed the StatterPlotView.ShowName into ShowChartOnly to match the property names if the (Indexed)DataTableView.
File size: 29.2 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2018 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("IndexedDataTable View")]
34  [Content(typeof(IndexedDataTable<>), true)]
35  public partial class IndexedDataTableView<T> : NamedItemView, IConfigureableView {
36    protected List<Series> invisibleSeries;
37    protected Dictionary<IObservableList<Tuple<T, double>>, IndexedDataRow<T>> valuesRowsTable;
38
39    public new IndexedDataTable<T> Content {
40      get { return (IndexedDataTable<T>)base.Content; }
41      set { base.Content = value; }
42    }
43
44    public IndexedDataTableView() {
45      InitializeComponent();
46      valuesRowsTable = new Dictionary<IObservableList<Tuple<T, double>>, IndexedDataRow<T>>();
47      invisibleSeries = new List<Series>();
48      chart.CustomizeAllChartAreas();
49      chart.ChartAreas[0].CursorX.Interval = 1;
50      chart.SuppressExceptions = true;
51      chart.ContextMenuStrip.Items.Add(configureToolStripMenuItem);
52    }
53
54    #region Event Handler Registration
55    protected override void DeregisterContentEvents() {
56      foreach (var row in Content.Rows)
57        DeregisterDataRowEvents(row);
58      Content.VisualPropertiesChanged -= new EventHandler(Content_VisualPropertiesChanged);
59      Content.Rows.ItemsAdded -= new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_ItemsAdded);
60      Content.Rows.ItemsRemoved -= new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_ItemsRemoved);
61      Content.Rows.ItemsReplaced -= new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_ItemsReplaced);
62      Content.Rows.CollectionReset -= new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_CollectionReset);
63      base.DeregisterContentEvents();
64    }
65
66    protected override void RegisterContentEvents() {
67      base.RegisterContentEvents();
68      Content.VisualPropertiesChanged += new EventHandler(Content_VisualPropertiesChanged);
69      Content.Rows.ItemsAdded += new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_ItemsAdded);
70      Content.Rows.ItemsRemoved += new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_ItemsRemoved);
71      Content.Rows.ItemsReplaced += new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_ItemsReplaced);
72      Content.Rows.CollectionReset += new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_CollectionReset);
73      foreach (var row in Content.Rows)
74        RegisterDataRowEvents(row);
75    }
76
77    /// <summary>
78    /// Automatically called for every existing data row and whenever a data row is added
79    /// to the data table. Do not call this method directly.
80    /// </summary>
81    /// <param name="row">The DataRow that was added.</param>
82    protected virtual void RegisterDataRowEvents(IndexedDataRow<T> 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<Tuple<T, double>>>(Values_ItemsAdded);
87      row.Values.ItemsRemoved += new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsRemoved);
88      row.Values.ItemsReplaced += new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsReplaced);
89      row.Values.ItemsMoved += new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsMoved);
90      row.Values.CollectionReset += new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_CollectionReset);
91    }
92
93    /// <summary>
94    /// Automatically called for every data row that is removed from the DataTable. Do
95    /// not directly call this method.
96    /// </summary>
97    /// <param name="row">The DataRow that was removed.</param>
98    protected virtual void DeregisterDataRowEvents(IndexedDataRow<T> row) {
99      row.Values.ItemsAdded -= new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsAdded);
100      row.Values.ItemsRemoved -= new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsRemoved);
101      row.Values.ItemsReplaced -= new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsReplaced);
102      row.Values.ItemsMoved -= new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsMoved);
103      row.Values.CollectionReset -= new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_CollectionReset);
104      valuesRowsTable.Remove(row.Values);
105      row.VisualPropertiesChanged -= new EventHandler(Row_VisualPropertiesChanged);
106      row.NameChanged -= new EventHandler(Row_NameChanged);
107    }
108    #endregion
109
110    protected override void OnContentChanged() {
111      base.OnContentChanged();
112      invisibleSeries.Clear();
113      chart.Titles[0].Text = string.Empty;
114      chart.ChartAreas[0].AxisX.Title = string.Empty;
115      chart.ChartAreas[0].AxisY.Title = string.Empty;
116      chart.ChartAreas[0].AxisY2.Title = string.Empty;
117      chart.ChartAreas[0].AxisX.IsLogarithmic = false;
118      chart.ChartAreas[0].AxisX2.IsLogarithmic = false;
119      chart.ChartAreas[0].AxisY.IsLogarithmic = false;
120      chart.ChartAreas[0].AxisY2.IsLogarithmic = false;
121      chart.Series.Clear();
122      if (Content != null) {
123        chart.Titles[0].Text = Content.Name;
124        foreach (var row in Content.Rows)
125          AddDataRow(row);
126        ConfigureChartArea(chart.ChartAreas[0]);
127        RecalculateAxesScale(chart.ChartAreas[0]);
128      }
129    }
130
131    protected override void SetEnabledStateOfControls() {
132      base.SetEnabledStateOfControls();
133      chart.Enabled = Content != null;
134    }
135
136    public void ShowConfiguration() {
137      if (Content != null) {
138        using (var dialog = new DataTableVisualPropertiesDialog<IndexedDataRow<T>>(Content)) {
139          dialog.ShowDialog(this);
140        }
141      } else MessageBox.Show("Nothing to configure.");
142    }
143
144    /// <summary>
145    /// Add the DataRow as a series to the chart.
146    /// </summary>
147    /// <param name="row">DataRow to add as series to the chart.</param>
148    protected virtual void AddDataRow(IndexedDataRow<T> row) {
149      if (row.Values.Count == 0) return;
150
151      Series series = new Series(row.Name);
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
157      chart.Series.Add(series);
158      ConfigureChartArea(chart.ChartAreas[0]);
159      RecalculateAxesScale(chart.ChartAreas[0]);
160      UpdateYCursorInterval();
161    }
162
163    private void ConfigureSeries(Series series, IndexedDataRow<T> row) {
164      RemoveCustomPropertyIfExists(series, "PointWidth");
165      series.BorderWidth = 1;
166      series.BorderDashStyle = ChartDashStyle.Solid;
167      series.BorderColor = Color.Empty;
168
169      series.Color = row.VisualProperties.Color;
170      series.IsVisibleInLegend = row.VisualProperties.IsVisibleInLegend;
171
172      series.SmartLabelStyle.Enabled = true;
173      series.SmartLabelStyle.AllowOutsidePlotArea = LabelOutsidePlotAreaStyle.No;
174      series.SmartLabelStyle.CalloutLineAnchorCapStyle = LineAnchorCapStyle.None;
175      series.SmartLabelStyle.CalloutLineColor = series.Color;
176      series.SmartLabelStyle.CalloutLineWidth = 2;
177      series.SmartLabelStyle.CalloutStyle = LabelCalloutStyle.Underlined;
178      series.SmartLabelStyle.IsOverlappedHidden = false;
179      series.SmartLabelStyle.MaxMovingDistance = 200;
180
181      switch (row.VisualProperties.ChartType) {
182        case DataRowVisualProperties.DataRowChartType.Line:
183          series.ChartType = SeriesChartType.FastLine;
184          series.BorderWidth = row.VisualProperties.LineWidth;
185          series.BorderDashStyle = ConvertLineStyle(row.VisualProperties.LineStyle);
186          break;
187        case DataRowVisualProperties.DataRowChartType.Bars:
188          // Bar is incompatible with anything but Bar and StackedBar*
189          if (!chart.Series.Any(x => x.ChartType != SeriesChartType.Bar && x.ChartType != SeriesChartType.StackedBar && x.ChartType != SeriesChartType.StackedBar100)) {
190            series.ChartType = SeriesChartType.Bar;
191            chart.ChartAreas[0].AxisX.Interval = 1;
192          } else {
193            series.ChartType = SeriesChartType.FastPoint; //default
194            row.VisualProperties.ChartType = DataRowVisualProperties.DataRowChartType.Points;
195          }
196          break;
197        case DataRowVisualProperties.DataRowChartType.Columns:
198          series.ChartType = SeriesChartType.Column;
199          break;
200        case DataRowVisualProperties.DataRowChartType.Points:
201          series.ChartType = SeriesChartType.FastPoint;
202          break;
203        case DataRowVisualProperties.DataRowChartType.StepLine:
204          series.ChartType = SeriesChartType.StepLine;
205          series.BorderWidth = row.VisualProperties.LineWidth;
206          series.BorderDashStyle = ConvertLineStyle(row.VisualProperties.LineStyle);
207          break;
208        default:
209          series.ChartType = SeriesChartType.FastPoint;
210          break;
211      }
212      series.YAxisType = row.VisualProperties.SecondYAxis ? AxisType.Secondary : AxisType.Primary;
213      series.XAxisType = row.VisualProperties.SecondXAxis ? AxisType.Secondary : AxisType.Primary;
214      if (row.VisualProperties.DisplayName.Trim() != String.Empty) series.LegendText = row.VisualProperties.DisplayName;
215      else series.LegendText = row.Name;
216      series.ToolTip = series.LegendText + " X = #VALX, Y = #VALY";
217    }
218
219    private void ConfigureChartArea(ChartArea area) {
220      if (Content.VisualProperties.TitleFont != null) chart.Titles[0].Font = Content.VisualProperties.TitleFont;
221      if (!Content.VisualProperties.TitleColor.IsEmpty) chart.Titles[0].ForeColor = Content.VisualProperties.TitleColor;
222      chart.Titles[0].Text = Content.VisualProperties.Title;
223      chart.Titles[0].Visible = !string.IsNullOrEmpty(Content.VisualProperties.Title);
224
225      if (Content.VisualProperties.AxisTitleFont != null) area.AxisX.TitleFont = Content.VisualProperties.AxisTitleFont;
226      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisX.TitleForeColor = Content.VisualProperties.AxisTitleColor;
227      area.AxisX.Title = Content.VisualProperties.XAxisTitle;
228
229      if (Content.VisualProperties.AxisTitleFont != null) area.AxisX2.TitleFont = Content.VisualProperties.AxisTitleFont;
230      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisX2.TitleForeColor = Content.VisualProperties.AxisTitleColor;
231      area.AxisX2.Title = Content.VisualProperties.SecondXAxisTitle;
232
233      if (Content.VisualProperties.AxisTitleFont != null) area.AxisY.TitleFont = Content.VisualProperties.AxisTitleFont;
234      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisY.TitleForeColor = Content.VisualProperties.AxisTitleColor;
235      area.AxisY.Title = Content.VisualProperties.YAxisTitle;
236
237      if (Content.VisualProperties.AxisTitleFont != null) area.AxisY2.TitleFont = Content.VisualProperties.AxisTitleFont;
238      if (!Content.VisualProperties.AxisTitleColor.IsEmpty) area.AxisY2.TitleForeColor = Content.VisualProperties.AxisTitleColor;
239      area.AxisY2.Title = Content.VisualProperties.SecondYAxisTitle;
240
241      if (typeof(T).Equals(typeof(DateTime))) {
242        area.AxisX.IntervalType = DateTimeIntervalType.Hours;
243        area.AxisX.LabelStyle.Format = "dd.MM.yyyy HH:mm";
244        area.AxisX.IntervalAutoMode = IntervalAutoMode.VariableCount;
245      }
246    }
247
248    private void RecalculateAxesScale(ChartArea area) {
249      // Reset the axes bounds so that RecalculateAxesScale() will assign new bounds
250      foreach (Axis a in area.Axes) {
251        a.Minimum = double.NaN;
252        a.Maximum = double.NaN;
253      }
254
255      if (chart.Series.Any(x => x.Enabled && x.XAxisType == AxisType.Primary && (x.Points.Count == 0 || x.Points.Any(y => y.XValue <= 0))))
256        area.AxisX.IsLogarithmic = false;
257      else area.AxisX.IsLogarithmic = Content.VisualProperties.XAxisLogScale;
258      if (chart.Series.Any(x => x.Enabled && x.XAxisType == AxisType.Secondary && (x.Points.Count == 0 || x.Points.Any(y => y.XValue <= 0))))
259        area.AxisX2.IsLogarithmic = false;
260      else area.AxisX2.IsLogarithmic = Content.VisualProperties.SecondXAxisLogScale;
261
262      if (chart.Series.Any(x => x.Enabled && x.YAxisType == AxisType.Primary && (x.Points.Count == 0 || x.Points.Any(y => y.YValues.Min() <= 0))))
263        area.AxisY.IsLogarithmic = false;
264      else area.AxisY.IsLogarithmic = Content.VisualProperties.YAxisLogScale;
265      if (chart.Series.Any(x => x.Enabled && x.YAxisType == AxisType.Secondary && (x.Points.Count == 0 || x.Points.Any(y => y.YValues.Min() <= 0))))
266        area.AxisY2.IsLogarithmic = false;
267      else area.AxisY2.IsLogarithmic = Content.VisualProperties.SecondYAxisLogScale;
268
269      area.RecalculateAxesScale();
270      area.AxisX.IsMarginVisible = false;
271      area.AxisX2.IsMarginVisible = false;
272
273      area.AxisX.IsStartedFromZero = Content.Rows.Where(x => !x.VisualProperties.SecondXAxis).Any(x => x.VisualProperties.StartIndexZero);
274      area.AxisX2.IsStartedFromZero = Content.Rows.Where(x => x.VisualProperties.SecondXAxis).Any(x => x.VisualProperties.StartIndexZero);
275
276      if (!Content.VisualProperties.XAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.XAxisMinimumFixedValue)) area.AxisX.Minimum = Content.VisualProperties.XAxisMinimumFixedValue;
277      if (!Content.VisualProperties.XAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.XAxisMaximumFixedValue)) area.AxisX.Maximum = Content.VisualProperties.XAxisMaximumFixedValue;
278      if (!Content.VisualProperties.SecondXAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.SecondXAxisMinimumFixedValue)) area.AxisX2.Minimum = Content.VisualProperties.SecondXAxisMinimumFixedValue;
279      if (!Content.VisualProperties.SecondXAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.SecondXAxisMaximumFixedValue)) area.AxisX2.Maximum = Content.VisualProperties.SecondXAxisMaximumFixedValue;
280      if (!Content.VisualProperties.YAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.YAxisMinimumFixedValue)) area.AxisY.Minimum = Content.VisualProperties.YAxisMinimumFixedValue;
281      if (!Content.VisualProperties.YAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.YAxisMaximumFixedValue)) area.AxisY.Maximum = Content.VisualProperties.YAxisMaximumFixedValue;
282      if (!Content.VisualProperties.SecondYAxisMinimumAuto && !double.IsNaN(Content.VisualProperties.SecondYAxisMinimumFixedValue)) area.AxisY2.Minimum = Content.VisualProperties.SecondYAxisMinimumFixedValue;
283      if (!Content.VisualProperties.SecondYAxisMaximumAuto && !double.IsNaN(Content.VisualProperties.SecondYAxisMaximumFixedValue)) area.AxisY2.Maximum = Content.VisualProperties.SecondYAxisMaximumFixedValue;
284      if (area.AxisX.Minimum >= area.AxisX.Maximum) area.AxisX.Maximum = area.AxisX.Minimum + 1;
285      if (area.AxisX2.Minimum >= area.AxisX2.Maximum) area.AxisX2.Maximum = area.AxisX2.Minimum + 1;
286      if (area.AxisY.Minimum >= area.AxisY.Maximum) area.AxisY.Maximum = area.AxisY.Minimum + 1;
287      if (area.AxisY2.Minimum >= area.AxisY2.Maximum) area.AxisY2.Maximum = area.AxisY2.Minimum + 1;
288    }
289
290    /// <summary>
291    /// Set the Y Cursor interval to visible points of enabled series.
292    /// </summary>
293    protected virtual void UpdateYCursorInterval() {
294      double interestingValuesRange = (
295        from series in chart.Series
296        where series.Enabled
297        let values = (from point in series.Points
298                      where !point.IsEmpty
299                      select point.YValues[0]).DefaultIfEmpty(1.0)
300        let range = values.Max() - values.Min()
301        where range > 0.0
302        select range
303        ).DefaultIfEmpty(1.0).Min();
304
305      double digits = (int)Math.Log10(interestingValuesRange) - 3;
306      double yZoomInterval = Math.Pow(10, digits);
307      this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval;
308    }
309
310
311    /// <summary>
312    /// Remove the corresponding series for a certain DataRow.
313    /// </summary>
314    /// <param name="row">DataRow which series should be removed.</param>
315    protected virtual void RemoveDataRow(IndexedDataRow<T> row) {
316      if (chart.Series.All(x => x.Name != row.Name)) return;
317      Series series = chart.Series[row.Name];
318      chart.Series.Remove(series);
319      if (invisibleSeries.Contains(series))
320        invisibleSeries.Remove(series);
321      RecalculateAxesScale(chart.ChartAreas[0]);
322    }
323
324    #region Event Handlers
325    #region Content Event Handlers
326    protected override void Content_NameChanged(object sender, EventArgs e) {
327      if (InvokeRequired)
328        Invoke(new EventHandler(Content_NameChanged), sender, e);
329      else {
330        chart.Titles[0].Text = Content.Name;
331        base.Content_NameChanged(sender, e);
332      }
333    }
334    private void Content_VisualPropertiesChanged(object sender, EventArgs e) {
335      if (InvokeRequired)
336        Invoke(new EventHandler(Content_VisualPropertiesChanged), sender, e);
337      else {
338        ConfigureChartArea(chart.ChartAreas[0]);
339        RecalculateAxesScale(chart.ChartAreas[0]); // axes min/max could have changed
340      }
341    }
342    #endregion
343    #region Rows Event Handlers
344    private void Rows_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedDataRow<T>> e) {
345      if (InvokeRequired)
346        Invoke(new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_ItemsAdded), sender, e);
347      else {
348        foreach (var row in e.Items) {
349          AddDataRow(row);
350          RegisterDataRowEvents(row);
351        }
352      }
353    }
354    private void Rows_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedDataRow<T>> e) {
355      if (InvokeRequired)
356        Invoke(new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_ItemsRemoved), sender, e);
357      else {
358        foreach (var row in e.Items) {
359          DeregisterDataRowEvents(row);
360          RemoveDataRow(row);
361        }
362      }
363    }
364    private void Rows_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<IndexedDataRow<T>> e) {
365      if (InvokeRequired)
366        Invoke(new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_ItemsReplaced), sender, e);
367      else {
368        foreach (var row in e.OldItems) {
369          DeregisterDataRowEvents(row);
370          RemoveDataRow(row);
371        }
372        foreach (var row in e.Items) {
373          AddDataRow(row);
374          RegisterDataRowEvents(row);
375        }
376      }
377    }
378    private void Rows_CollectionReset(object sender, CollectionItemsChangedEventArgs<IndexedDataRow<T>> e) {
379      if (InvokeRequired)
380        Invoke(new CollectionItemsChangedEventHandler<IndexedDataRow<T>>(Rows_CollectionReset), sender, e);
381      else {
382        foreach (var row in e.OldItems) {
383          DeregisterDataRowEvents(row);
384          RemoveDataRow(row);
385        }
386        foreach (var row in e.Items) {
387          AddDataRow(row);
388          RegisterDataRowEvents(row);
389        }
390      }
391    }
392    #endregion
393    #region Row Event Handlers
394    private void Row_VisualPropertiesChanged(object sender, EventArgs e) {
395      if (InvokeRequired)
396        Invoke(new EventHandler(Row_VisualPropertiesChanged), sender, e);
397      else {
398        var row = (IndexedDataRow<T>)sender;
399        Series series = chart.Series[row.Name];
400        series.Points.Clear();
401        ConfigureSeries(series, row);
402        FillSeriesWithRowValues(series, row);
403        RecalculateAxesScale(chart.ChartAreas[0]);
404      }
405    }
406    private void Row_NameChanged(object sender, EventArgs e) {
407      if (InvokeRequired)
408        Invoke(new EventHandler(Row_NameChanged), sender, e);
409      else {
410        var row = (IndexedDataRow<T>)sender;
411        chart.Series[row.Name].Name = row.Name;
412      }
413    }
414    #endregion
415    #region Values Event Handlers
416    private void Values_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<Tuple<T, double>>> e) {
417      if (InvokeRequired)
418        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsAdded), sender, e);
419      else {
420        IndexedDataRow<T> row = null;
421        valuesRowsTable.TryGetValue((IObservableList<Tuple<T, double>>)sender, out row);
422        if (row != null) {
423          Series rowSeries = chart.Series[row.Name];
424          if (!invisibleSeries.Contains(rowSeries)) {
425            rowSeries.Points.Clear();
426            FillSeriesWithRowValues(rowSeries, row);
427            RecalculateAxesScale(chart.ChartAreas[0]);
428            UpdateYCursorInterval();
429          }
430        } else AddDataRow((IndexedDataRow<T>)sender);
431      }
432    }
433    private void Values_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Tuple<T, double>>> e) {
434      if (InvokeRequired)
435        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsRemoved), sender, e);
436      else {
437        IndexedDataRow<T> row = null;
438        valuesRowsTable.TryGetValue((IObservableList<Tuple<T, double>>)sender, out row);
439        if (row != null) {
440          if (row.Values.Count == 0) {
441            RemoveDataRow(row);
442          } else {
443            Series rowSeries = chart.Series[row.Name];
444            if (!invisibleSeries.Contains(rowSeries)) {
445              rowSeries.Points.Clear();
446              FillSeriesWithRowValues(rowSeries, row);
447              RecalculateAxesScale(chart.ChartAreas[0]);
448              UpdateYCursorInterval();
449            }
450          }
451        }
452      }
453    }
454    private void Values_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<IndexedItem<Tuple<T, double>>> e) {
455      if (InvokeRequired)
456        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsReplaced), sender, e);
457      else {
458        IndexedDataRow<T> row = null;
459        valuesRowsTable.TryGetValue((IObservableList<Tuple<T, double>>)sender, out row);
460        if (row != null) {
461          if (row.Values.Count == 0) {
462            RemoveDataRow(row);
463          } else {
464            Series rowSeries = chart.Series[row.Name];
465            if (!invisibleSeries.Contains(rowSeries)) {
466              if (row.VisualProperties.ChartType == DataRowVisualProperties.DataRowChartType.Histogram) {
467                rowSeries.Points.Clear();
468                FillSeriesWithRowValues(rowSeries, row);
469              } else {
470                foreach (IndexedItem<Tuple<T, double>> item in e.Items) {
471                  rowSeries.Points[item.Index].SetValueXY(item.Value.Item1, item.Value.Item2);
472                }
473              }
474              RecalculateAxesScale(chart.ChartAreas[0]);
475              UpdateYCursorInterval();
476            }
477          }
478        }
479      }
480    }
481    private void Values_ItemsMoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Tuple<T, double>>> e) {
482      if (InvokeRequired)
483        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_ItemsMoved), sender, e);
484      else {
485        IndexedDataRow<T> row = null;
486        valuesRowsTable.TryGetValue((IObservableList<Tuple<T, double>>)sender, out row);
487        if (row != null) {
488          Series rowSeries = chart.Series[row.Name];
489          if (!invisibleSeries.Contains(rowSeries)) {
490            rowSeries.Points.Clear();
491            FillSeriesWithRowValues(rowSeries, row);
492            RecalculateAxesScale(chart.ChartAreas[0]);
493            UpdateYCursorInterval();
494          }
495        }
496      }
497    }
498
499    private void Values_CollectionReset(object sender, CollectionItemsChangedEventArgs<IndexedItem<Tuple<T, double>>> e) {
500      if (InvokeRequired)
501        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<Tuple<T, double>>>(Values_CollectionReset), sender, e);
502      else {
503        IndexedDataRow<T> row = null;
504        valuesRowsTable.TryGetValue((IObservableList<Tuple<T, double>>)sender, out row);
505        if (row != null) {
506          if (row.Values.Count == 0) {
507            RemoveDataRow(row);
508          } else {
509            Series rowSeries = chart.Series[row.Name];
510            if (!invisibleSeries.Contains(rowSeries)) {
511              rowSeries.Points.Clear();
512              FillSeriesWithRowValues(rowSeries, row);
513              RecalculateAxesScale(chart.ChartAreas[0]);
514              UpdateYCursorInterval();
515            }
516          }
517        }
518      }
519    }
520    #endregion
521    private void configureToolStripMenuItem_Click(object sender, EventArgs e) {
522      ShowConfiguration();
523    }
524    #endregion
525
526    #region Chart Event Handlers
527    private void chart_MouseDown(object sender, MouseEventArgs e) {
528      HitTestResult result = chart.HitTest(e.X, e.Y);
529      if (result.ChartElementType == ChartElementType.LegendItem) {
530        ToggleSeriesVisible(result.Series);
531      }
532    }
533    private void chart_MouseMove(object sender, MouseEventArgs e) {
534      HitTestResult result = chart.HitTest(e.X, e.Y);
535      if (result.ChartElementType == ChartElementType.LegendItem)
536        this.Cursor = Cursors.Hand;
537      else
538        this.Cursor = Cursors.Default;
539    }
540    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
541      foreach (LegendItem legendItem in e.LegendItems) {
542        var series = chart.Series[legendItem.SeriesName];
543        if (series != null) {
544          bool seriesIsInvisible = invisibleSeries.Contains(series);
545          foreach (LegendCell cell in legendItem.Cells) {
546            cell.ForeColor = seriesIsInvisible ? Color.Gray : Color.Black;
547          }
548        }
549      }
550    }
551    #endregion
552
553    private void ToggleSeriesVisible(Series series) {
554      if (!invisibleSeries.Contains(series)) {
555        series.Points.Clear();
556        invisibleSeries.Add(series);
557      } else {
558        invisibleSeries.Remove(series);
559        if (Content != null) {
560
561          var row = (from r in Content.Rows
562                     where r.Name == series.Name
563                     select r).Single();
564          FillSeriesWithRowValues(series, row);
565          this.chart.Legends[series.Legend].ForeColor = Color.Black;
566          RecalculateAxesScale(chart.ChartAreas[0]);
567          UpdateYCursorInterval();
568        }
569      }
570    }
571
572    private void FillSeriesWithRowValues(Series series, IndexedDataRow<T> row) {
573      for (int i = 0; i < row.Values.Count; i++) {
574        var value = row.Values[i];
575        if (IsInvalidValue(value.Item2)) continue;
576        var point = new DataPoint();
577        point.SetValueXY(value.Item1, value.Item2);
578        if (i == row.Values.Count - 1) {
579          point.Label = series.Name;
580          point.MarkerStyle = MarkerStyle.Cross;
581          point.MarkerSize = 15;
582          point.MarkerBorderWidth = 1;
583        }
584        series.Points.Add(point);
585      }
586    }
587
588    #region Helpers
589    protected void RemoveCustomPropertyIfExists(Series series, string property) {
590      if (series.IsCustomPropertySet(property)) series.DeleteCustomProperty(property);
591    }
592
593    private ChartDashStyle ConvertLineStyle(DataRowVisualProperties.DataRowLineStyle dataRowLineStyle) {
594      switch (dataRowLineStyle) {
595        case DataRowVisualProperties.DataRowLineStyle.Dash:
596          return ChartDashStyle.Dash;
597        case DataRowVisualProperties.DataRowLineStyle.DashDot:
598          return ChartDashStyle.DashDot;
599        case DataRowVisualProperties.DataRowLineStyle.DashDotDot:
600          return ChartDashStyle.DashDotDot;
601        case DataRowVisualProperties.DataRowLineStyle.Dot:
602          return ChartDashStyle.Dot;
603        case DataRowVisualProperties.DataRowLineStyle.NotSet:
604          return ChartDashStyle.NotSet;
605        case DataRowVisualProperties.DataRowLineStyle.Solid:
606          return ChartDashStyle.Solid;
607        default:
608          return ChartDashStyle.NotSet;
609      }
610    }
611
612    /// <summary>
613    /// Determines whether a double value can be displayed (converted to Decimal and not an NaN).
614    /// </summary>
615    /// <param name="x">The number to check.</param>
616    /// <returns><code>true</code> if the value can be safely shwon in the chart,
617    /// <code>false</code> otherwise.</returns>
618    protected static bool IsInvalidValue(double x) {
619      return double.IsNaN(x) || x < (double)decimal.MinValue || x > (double)decimal.MaxValue;
620    }
621    #endregion
622  }
623}
Note: See TracBrowser for help on using the repository browser.