source: stable/HeuristicLab.Analysis.Views/3.3/DataTableView.cs @ 15097

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

#2713 #2715 #2765
Merged to stable

  • 14435-14439,14493,14516,14519,14982,14987,14992,15042 (from #2713)
  • 14457-14458,14508,14582,14740,14984,15068,15095 (from #2715)
  • 14860-14861 (from #2765)
File size: 34.3 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
352        chart.Update(); // side-by-side and stacked histograms are not always correctly displayed without an update
353        // (chart update is required before the series are updated, otherwise the widths of the bars are updated incorrectly)
354        foreach (var row in Content.Rows.Where(r => r.VisualProperties.ChartType == DataRowVisualProperties.DataRowChartType.Histogram))
355          Row_VisualPropertiesChanged(row, EventArgs.Empty); // Histogram properties could have changed
356      }
357    }
358    #endregion
359    #region Rows Event Handlers
360    private void Rows_ItemsAdded(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
361      if (InvokeRequired)
362        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsAdded), sender, e);
363      else {
364        AddDataRows(e.Items);
365      }
366    }
367    private void Rows_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
368      if (InvokeRequired)
369        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsRemoved), sender, e);
370      else {
371        RemoveDataRows(e.Items);
372      }
373    }
374    private void Rows_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
375      if (InvokeRequired)
376        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_ItemsReplaced), sender, e);
377      else {
378        RemoveDataRows(e.OldItems);
379        AddDataRows(e.Items);
380      }
381    }
382    private void Rows_CollectionReset(object sender, CollectionItemsChangedEventArgs<DataRow> e) {
383      if (InvokeRequired)
384        Invoke(new CollectionItemsChangedEventHandler<DataRow>(Rows_CollectionReset), sender, e);
385      else {
386        RemoveDataRows(e.OldItems);
387        AddDataRows(e.Items);
388      }
389    }
390    #endregion
391    #region Row Event Handlers
392    private void Row_VisualPropertiesChanged(object sender, EventArgs e) {
393      if (InvokeRequired)
394        Invoke(new EventHandler(Row_VisualPropertiesChanged), sender, e);
395      else {
396        DataRow row = (DataRow)sender;
397        Series series = chart.Series[row.Name];
398        series.Points.Clear();
399        ConfigureSeries(series, row);
400        if (!invisibleSeries.Contains(series)) {
401          FillSeriesWithRowValues(series, row);
402          RecalculateAxesScale(chart.ChartAreas[0]);
403          UpdateHistogramTransparency();
404        }
405      }
406    }
407    private void Row_NameChanged(object sender, EventArgs e) {
408      if (InvokeRequired)
409        Invoke(new EventHandler(Row_NameChanged), sender, e);
410      else {
411        DataRow row = (DataRow)sender;
412        chart.Series[row.Name].Name = row.Name;
413      }
414    }
415    #endregion
416    #region Values Event Handlers
417    private void Values_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<double>> e) {
418      if (InvokeRequired)
419        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsAdded), sender, e);
420      else {
421        DataRow row = null;
422        valuesRowsTable.TryGetValue((IObservableList<double>)sender, out row);
423        if (row != null) {
424          Series rowSeries = chart.Series[row.Name];
425          if (!invisibleSeries.Contains(rowSeries)) {
426            rowSeries.Points.Clear();
427            FillSeriesWithRowValues(rowSeries, row);
428            RecalculateAxesScale(chart.ChartAreas[0]);
429            UpdateYCursorInterval();
430          }
431        }
432      }
433    }
434    private void Values_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<double>> e) {
435      if (InvokeRequired)
436        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsRemoved), sender, e);
437      else {
438        DataRow row = null;
439        valuesRowsTable.TryGetValue((IObservableList<double>)sender, out row);
440        if (row != null) {
441          Series rowSeries = chart.Series[row.Name];
442          if (!invisibleSeries.Contains(rowSeries)) {
443            rowSeries.Points.Clear();
444            FillSeriesWithRowValues(rowSeries, row);
445            RecalculateAxesScale(chart.ChartAreas[0]);
446            UpdateYCursorInterval();
447          }
448        }
449      }
450    }
451    private void Values_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<IndexedItem<double>> e) {
452      if (InvokeRequired)
453        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsReplaced), sender, e);
454      else {
455        DataRow row = null;
456        valuesRowsTable.TryGetValue((IObservableList<double>)sender, out row);
457        if (row != null) {
458          Series rowSeries = chart.Series[row.Name];
459          if (!invisibleSeries.Contains(rowSeries)) {
460            if (row.VisualProperties.ChartType == DataRowVisualProperties.DataRowChartType.Histogram) {
461              rowSeries.Points.Clear();
462              FillSeriesWithRowValues(rowSeries, row);
463            } else {
464              foreach (IndexedItem<double> item in e.Items) {
465                if (IsInvalidValue(item.Value))
466                  rowSeries.Points[item.Index].IsEmpty = true;
467                else {
468                  rowSeries.Points[item.Index].YValues = new double[] { item.Value };
469                  rowSeries.Points[item.Index].IsEmpty = false;
470                }
471              }
472            }
473            RecalculateAxesScale(chart.ChartAreas[0]);
474            UpdateYCursorInterval();
475          }
476        }
477      }
478    }
479    private void Values_ItemsMoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<double>> e) {
480      if (InvokeRequired)
481        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_ItemsMoved), sender, e);
482      else {
483        DataRow row = null;
484        valuesRowsTable.TryGetValue((IObservableList<double>)sender, out row);
485        if (row != null) {
486          Series rowSeries = chart.Series[row.Name];
487          if (!invisibleSeries.Contains(rowSeries)) {
488            rowSeries.Points.Clear();
489            FillSeriesWithRowValues(rowSeries, row);
490            RecalculateAxesScale(chart.ChartAreas[0]);
491            UpdateYCursorInterval();
492          }
493        }
494      }
495    }
496
497    private void Values_CollectionReset(object sender, CollectionItemsChangedEventArgs<IndexedItem<double>> e) {
498      if (InvokeRequired)
499        Invoke(new CollectionItemsChangedEventHandler<IndexedItem<double>>(Values_CollectionReset), sender, e);
500      else {
501        DataRow row = null;
502        valuesRowsTable.TryGetValue((IObservableList<double>)sender, out row);
503        if (row != null) {
504          Series rowSeries = chart.Series[row.Name];
505          if (!invisibleSeries.Contains(rowSeries)) {
506            rowSeries.Points.Clear();
507            FillSeriesWithRowValues(rowSeries, row);
508            RecalculateAxesScale(chart.ChartAreas[0]);
509            UpdateYCursorInterval();
510          }
511        }
512      }
513    }
514    #endregion
515    private void configureToolStripMenuItem_Click(object sender, EventArgs e) {
516      ShowConfiguration();
517    }
518    #endregion
519
520    #region Chart Event Handlers
521    private void chart_MouseDown(object sender, MouseEventArgs e) {
522      HitTestResult result = chart.HitTest(e.X, e.Y);
523      if (result.ChartElementType == ChartElementType.LegendItem) {
524        ToggleSeriesVisible(result.Series);
525      }
526    }
527    private void chart_MouseMove(object sender, MouseEventArgs e) {
528      HitTestResult result = chart.HitTest(e.X, e.Y);
529      if (result.ChartElementType == ChartElementType.LegendItem)
530        this.Cursor = Cursors.Hand;
531      else
532        this.Cursor = Cursors.Default;
533    }
534    private void chart_CustomizeLegend(object sender, CustomizeLegendEventArgs e) {
535      foreach (LegendItem legendItem in e.LegendItems) {
536        var series = chart.Series[legendItem.SeriesName];
537        if (series != null) {
538          bool seriesIsInvisible = invisibleSeries.Contains(series);
539          foreach (LegendCell cell in legendItem.Cells) {
540            cell.ForeColor = seriesIsInvisible ? Color.Gray : Color.Black;
541          }
542        }
543      }
544    }
545    #endregion
546
547    private void ToggleSeriesVisible(Series series) {
548      if (!invisibleSeries.Contains(series)) {
549        series.Points.Clear();
550        invisibleSeries.Add(series);
551      } else {
552        invisibleSeries.Remove(series);
553        if (Content != null) {
554
555          var row = (from r in Content.Rows
556                     where r.Name == series.Name
557                     select r).Single();
558          FillSeriesWithRowValues(series, row);
559          this.chart.Legends[series.Legend].ForeColor = Color.Black;
560          RecalculateAxesScale(chart.ChartAreas[0]);
561          UpdateYCursorInterval();
562        }
563      }
564    }
565
566    private void FillSeriesWithRowValues(Series series, DataRow row) {
567      switch (row.VisualProperties.ChartType) {
568        case DataRowVisualProperties.DataRowChartType.Histogram:
569          // when a single histogram is updated, all histograms must be updated. otherwise the value ranges may not be equal.
570          var histograms = Content.Rows
571            .Where(r => r.VisualProperties.ChartType == DataRowVisualProperties.DataRowChartType.Histogram)
572            .ToList();
573          CalculateHistogram(series, row, histograms);
574          foreach (var h in from r in histograms
575                            where r != row
576                            let s = chart.Series.FindByName(r.Name)
577                            where s != null
578                            where !invisibleSeries.Contains(s)
579                            select new { row = r, series = s }) {
580            h.series.Points.Clear();
581            CalculateHistogram(h.series, h.row, histograms);
582          }
583          break;
584        default: {
585            bool yLogarithmic = series.YAxisType == AxisType.Primary
586                                  ? Content.VisualProperties.YAxisLogScale
587                                  : Content.VisualProperties.SecondYAxisLogScale;
588            bool xLogarithmic = series.XAxisType == AxisType.Primary
589                                  ? Content.VisualProperties.XAxisLogScale
590                                  : Content.VisualProperties.SecondXAxisLogScale;
591            for (int i = 0; i < row.Values.Count; i++) {
592              var value = row.Values[i];
593              var point = new DataPoint();
594              point.XValue = row.VisualProperties.StartIndexZero && !xLogarithmic ? i : i + 1;
595              if (IsInvalidValue(value) || (yLogarithmic && value <= 0))
596                point.IsEmpty = true;
597              else
598                point.YValues = new double[] { value };
599              series.Points.Add(point);
600            }
601          }
602          break;
603      }
604    }
605
606    protected virtual void CalculateHistogram(Series series, DataRow row, IEnumerable<DataRow> histogramRows) {
607      series.Points.Clear();
608      if (!row.Values.Any()) return;
609
610      var validValues = histogramRows.SelectMany(r => r.Values).Where(x => !IsInvalidValue(x)).ToList();
611      if (!validValues.Any()) return;
612
613      int bins = Content.VisualProperties.HistogramBins;
614      decimal minValue = (decimal)validValues.Min();
615      decimal maxValue = (decimal)validValues.Max();
616      decimal intervalWidth = (maxValue - minValue) / bins;
617      if (intervalWidth < 0) return;
618      if (intervalWidth == 0) {
619        series.Points.AddXY(minValue, row.Values.Count);
620        return;
621      }
622
623      if (!Content.VisualProperties.HistogramExactBins) {
624        intervalWidth = (decimal)HumanRoundRange((double)intervalWidth);
625        minValue = Math.Floor(minValue / intervalWidth) * intervalWidth;
626        maxValue = Math.Ceiling(maxValue / intervalWidth) * intervalWidth;
627      }
628
629      decimal intervalCenter = intervalWidth / 2;
630
631      decimal min = 0.0m, max = 0.0m;
632      if (!Double.IsNaN(Content.VisualProperties.XAxisMinimumFixedValue) && !Content.VisualProperties.XAxisMinimumAuto)
633        min = (decimal)Content.VisualProperties.XAxisMinimumFixedValue;
634      else min = minValue;
635      if (!Double.IsNaN(Content.VisualProperties.XAxisMaximumFixedValue) && !Content.VisualProperties.XAxisMaximumAuto)
636        max = (decimal)Content.VisualProperties.XAxisMaximumFixedValue;
637      else max = maxValue + intervalWidth;
638
639      double axisInterval = (double)intervalWidth / row.VisualProperties.ScaleFactor;
640
641      var area = chart.ChartAreas[0];
642      area.AxisX.Interval = axisInterval;
643
644      series.SetCustomProperty("PointWidth", "1"); // 0.8 is the default value
645
646      // get the range or intervals which define the grouping of the frequency values
647      var range = Range(min, max, intervalWidth).Skip(1).ToList();
648
649      // aggregate the row values by unique key and frequency value
650      var valueFrequencies = (from v in row.Values
651                              where !IsInvalidValue(v)
652                              orderby v
653                              group v by v into g
654                              select new Tuple<double, double>(g.First(), g.Count())).ToList();
655
656      // ensure that each column is displayed completely on the chart by adding two dummy datapoints on the upper and lower range
657      series.Points.Add(new DataPoint((double)(min - intervalWidth), 0));
658      series.Points.Add(new DataPoint((double)(max + intervalWidth), 0));
659
660      // add data points
661      int j = 0;
662      int overallCount = row.Values.Count(x => !IsInvalidValue(x));
663      foreach (var d in range) {
664        double sum = 0.0;
665        // sum the frequency values that fall within the same interval
666        while (j < valueFrequencies.Count && (decimal)valueFrequencies[j].Item1 < d) {
667          sum += valueFrequencies[j].Item2;
668          ++j;
669        }
670        string xAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.XAxisTitle)
671                              ? "X"
672                              : Content.VisualProperties.XAxisTitle;
673        string yAxisTitle = string.IsNullOrEmpty(Content.VisualProperties.YAxisTitle)
674                              ? "Y"
675                              : Content.VisualProperties.YAxisTitle;
676        series.Points.Add(new DataPoint((double)(d - intervalCenter), sum) {
677          ToolTip =
678            string.Format("{0}: [{1} - {2})", xAxisTitle, (d - intervalWidth), d) + Environment.NewLine +
679            string.Format("{0}: {1} ({2:F2}%)", yAxisTitle, sum, sum / overallCount * 100)
680        });
681      }
682    }
683
684    #region Helpers
685    public static IEnumerable<decimal> Range(decimal min, decimal max, decimal step) {
686      decimal i;
687      for (i = min; i <= max; i += step)
688        yield return i;
689
690      if (i != max + step)
691        yield return i;
692    }
693
694    protected void RemoveCustomPropertyIfExists(Series series, string property) {
695      if (series.IsCustomPropertySet(property)) series.DeleteCustomProperty(property);
696    }
697
698    private double HumanRoundRange(double range) {
699      double base10 = Math.Pow(10.0, Math.Floor(Math.Log10(range)));
700      double rounding = range / base10;
701      if (rounding <= 1.5) rounding = 1;
702      else if (rounding <= 2.25) rounding = 2;
703      else if (rounding <= 3.75) rounding = 2.5;
704      else if (rounding <= 7.5) rounding = 5;
705      else rounding = 10;
706      return rounding * base10;
707    }
708
709    private double HumanRoundMax(double max) {
710      double base10;
711      if (max > 0) base10 = Math.Pow(10.0, Math.Floor(Math.Log10(max)));
712      else base10 = Math.Pow(10.0, Math.Ceiling(Math.Log10(-max)));
713      double rounding = (max > 0) ? base10 : -base10;
714      while (rounding < max) rounding += base10;
715      return rounding;
716    }
717
718    private ChartDashStyle ConvertLineStyle(DataRowVisualProperties.DataRowLineStyle dataRowLineStyle) {
719      switch (dataRowLineStyle) {
720        case DataRowVisualProperties.DataRowLineStyle.Dash:
721          return ChartDashStyle.Dash;
722        case DataRowVisualProperties.DataRowLineStyle.DashDot:
723          return ChartDashStyle.DashDot;
724        case DataRowVisualProperties.DataRowLineStyle.DashDotDot:
725          return ChartDashStyle.DashDotDot;
726        case DataRowVisualProperties.DataRowLineStyle.Dot:
727          return ChartDashStyle.Dot;
728        case DataRowVisualProperties.DataRowLineStyle.NotSet:
729          return ChartDashStyle.NotSet;
730        case DataRowVisualProperties.DataRowLineStyle.Solid:
731          return ChartDashStyle.Solid;
732        default:
733          return ChartDashStyle.NotSet;
734      }
735    }
736
737    protected static bool IsInvalidValue(double x) {
738      return double.IsNaN(x) || x < (double)decimal.MinValue || x > (double)decimal.MaxValue;
739    }
740    #endregion
741  }
742}
Note: See TracBrowser for help on using the repository browser.