source: trunk/sources/HeuristicLab.Optimization.Views/3.3/RunCollectionViews/RunCollectionBoxPlotView.cs @ 8738

Last change on this file since 8738 was 8738, checked in by mkommend, 8 years ago

#1673: Added new property AlgorithmName to the RunCollection and synced the property with the name of the surrounding IOptimizer. The AlgorithmName is used by the RunCollectionViews as prefix for its caption if it was set.

File size: 18.6 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2012 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.Linq;
25using System.Windows.Forms;
26using System.Windows.Forms.DataVisualization.Charting;
27using HeuristicLab.Common;
28using HeuristicLab.Core;
29using HeuristicLab.Data;
30using HeuristicLab.MainForm;
31using HeuristicLab.MainForm.WindowsForms;
32
33namespace HeuristicLab.Optimization.Views {
34  [View("RunCollection BoxPlots")]
35  [Content(typeof(RunCollection), false)]
36  public partial class RunCollectionBoxPlotView : AsynchronousContentView {
37    private enum AxisDimension { Color = 0 }
38    private const string BoxPlotSeriesName = "BoxPlotSeries";
39    private const string BoxPlotChartAreaName = "BoxPlotChartArea";
40
41    private bool suppressUpdates = false;
42    private string xAxisValue;
43    private string yAxisValue;
44    private Dictionary<int, Dictionary<object, double>> categoricalMapping;
45    private SortedDictionary<double, Series> seriesCache;
46
47    public RunCollectionBoxPlotView() {
48      InitializeComponent();
49      categoricalMapping = new Dictionary<int, Dictionary<object, double>>();
50      seriesCache = new SortedDictionary<double, Series>();
51      chart.ChartAreas[0].Visible = false;
52      chart.Series.Clear();
53      chart.ChartAreas.Add(BoxPlotChartAreaName);
54      chart.CustomizeAllChartAreas();
55      chart.ChartAreas[BoxPlotChartAreaName].Axes.ToList().ForEach(x => { x.ScaleView.Zoomable = true; x.ScaleView.MinSize = 0; });
56      chart.ChartAreas[BoxPlotChartAreaName].CursorX.Interval = 0.5;
57      chart.ChartAreas[BoxPlotChartAreaName].CursorY.Interval = 1e-5;
58    }
59
60    public new RunCollection Content {
61      get { return (RunCollection)base.Content; }
62      set { base.Content = value; }
63    }
64    public IStringConvertibleMatrix Matrix {
65      get { return this.Content; }
66    }
67
68    #region RunCollection and Run events
69    protected override void RegisterContentEvents() {
70      base.RegisterContentEvents();
71      Content.Reset += new EventHandler(Content_Reset);
72      Content.ColumnNamesChanged += new EventHandler(Content_ColumnNamesChanged);
73      Content.ItemsAdded += new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsAdded);
74      Content.ItemsRemoved += new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsRemoved);
75      Content.CollectionReset += new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_CollectionReset);
76      Content.UpdateOfRunsInProgressChanged += new EventHandler(Content_UpdateOfRunsInProgressChanged);
77      Content.AlgorithmNameChanged += new EventHandler(Content_AlgorithmNameChanged);
78      RegisterRunEvents(Content);
79    }
80    protected override void DeregisterContentEvents() {
81      base.DeregisterContentEvents();
82      Content.Reset -= new EventHandler(Content_Reset);
83      Content.ColumnNamesChanged -= new EventHandler(Content_ColumnNamesChanged);
84      Content.ItemsAdded -= new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsAdded);
85      Content.ItemsRemoved -= new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsRemoved);
86      Content.CollectionReset -= new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_CollectionReset);
87      Content.UpdateOfRunsInProgressChanged -= new EventHandler(Content_UpdateOfRunsInProgressChanged);
88      Content.AlgorithmNameChanged -= new EventHandler(Content_AlgorithmNameChanged);
89      DeregisterRunEvents(Content);
90    }
91
92    protected virtual void RegisterRunEvents(IEnumerable<IRun> runs) {
93      foreach (IRun run in runs)
94        run.Changed += new EventHandler(run_Changed);
95    }
96    protected virtual void DeregisterRunEvents(IEnumerable<IRun> runs) {
97      foreach (IRun run in runs)
98        run.Changed -= new EventHandler(run_Changed);
99    }
100
101    private void Content_CollectionReset(object sender, HeuristicLab.Collections.CollectionItemsChangedEventArgs<IRun> e) {
102      DeregisterRunEvents(e.OldItems);
103      RegisterRunEvents(e.Items);
104    }
105    private void Content_ItemsRemoved(object sender, HeuristicLab.Collections.CollectionItemsChangedEventArgs<IRun> e) {
106      DeregisterRunEvents(e.Items);
107    }
108    private void Content_ItemsAdded(object sender, HeuristicLab.Collections.CollectionItemsChangedEventArgs<IRun> e) {
109      RegisterRunEvents(e.Items);
110    }
111    private void Content_UpdateOfRunsInProgressChanged(object sender, EventArgs e) {
112      if (InvokeRequired)
113        Invoke(new EventHandler(Content_UpdateOfRunsInProgressChanged), sender, e);
114      else {
115        suppressUpdates = Content.UpdateOfRunsInProgress;
116        if (!suppressUpdates) UpdateDataPoints();
117      }
118    }
119
120    private void Content_Reset(object sender, EventArgs e) {
121      if (InvokeRequired)
122        Invoke(new EventHandler(Content_Reset), sender, e);
123      else {
124        this.categoricalMapping.Clear();
125        UpdateDataPoints();
126      }
127    }
128    private void Content_ColumnNamesChanged(object sender, EventArgs e) {
129      if (InvokeRequired)
130        Invoke(new EventHandler(Content_ColumnNamesChanged), sender, e);
131      else {
132        UpdateComboBoxes();
133      }
134    }
135    private void run_Changed(object sender, EventArgs e) {
136      if (InvokeRequired)
137        this.Invoke(new EventHandler(run_Changed), sender, e);
138      else if (!suppressUpdates) {
139        IRun run = (IRun)sender;
140        UpdateDataPoints();
141      }
142    }
143
144    private void Content_AlgorithmNameChanged(object sender, EventArgs e) {
145      if (InvokeRequired)
146        Invoke(new EventHandler(Content_AlgorithmNameChanged), sender, e);
147      else UpdateCaption();
148    }
149    #endregion
150
151    #region update comboboxes, datapoints, runs
152    protected override void OnContentChanged() {
153      base.OnContentChanged();
154      this.categoricalMapping.Clear();
155      UpdateComboBoxes();
156      UpdateDataPoints();
157      UpdateCaption();
158    }
159
160    private void UpdateCaption() {
161      Caption = Content != null ? Content.AlgorithmName + "Box Plots" : ViewAttribute.GetViewName(GetType());
162    }
163
164    private void UpdateComboBoxes() {
165      string selectedXAxis = (string)this.xAxisComboBox.SelectedItem;
166      string selectedYAxis = (string)this.yAxisComboBox.SelectedItem;
167      this.xAxisComboBox.Items.Clear();
168      this.yAxisComboBox.Items.Clear();
169      if (Content != null) {
170        string[] additionalAxisDimension = Enum.GetNames(typeof(AxisDimension));
171        this.xAxisComboBox.Items.AddRange(additionalAxisDimension);
172        this.xAxisComboBox.Items.AddRange(Matrix.ColumnNames.ToArray());
173        this.yAxisComboBox.Items.AddRange(additionalAxisDimension);
174        this.yAxisComboBox.Items.AddRange(Matrix.ColumnNames.ToArray());
175
176        bool changed = false;
177        if (selectedXAxis != null && xAxisComboBox.Items.Contains(selectedXAxis)) {
178          xAxisComboBox.SelectedItem = selectedXAxis;
179          changed = true;
180        }
181        if (selectedYAxis != null && yAxisComboBox.Items.Contains(selectedYAxis)) {
182          yAxisComboBox.SelectedItem = selectedYAxis;
183          changed = true;
184        }
185        if (changed)
186          UpdateDataPoints();
187      }
188    }
189
190    private void UpdateDataPoints() {
191      this.chart.Series.Clear();
192      this.seriesCache.Clear();
193      if (Content != null) {
194        foreach (IRun run in this.Content.Where(r => r.Visible))
195          this.AddDataPoint(run);
196        foreach (Series s in this.seriesCache.Values)
197          this.chart.Series.Add(s);
198
199        UpdateStatistics();
200        if (seriesCache.Count > 0) {
201          Series boxPlotSeries = CreateBoxPlotSeries();
202          this.chart.Series.Add(boxPlotSeries);
203        }
204
205        UpdateAxisLabels();
206      }
207      UpdateNoRunsVisibleLabel();
208    }
209
210    private void UpdateStatistics() {
211      DoubleMatrix matrix = new DoubleMatrix(9, seriesCache.Count);
212      matrix.SortableView = false;
213      List<string> columnNames = new List<string>();
214      foreach (Series series in seriesCache.Values) {
215        DataPoint datapoint = series.Points.FirstOrDefault();
216        if (datapoint != null) {
217          IRun run = (IRun)datapoint.Tag;
218          string selectedAxis = (string)xAxisComboBox.SelectedItem;
219          IItem value = null;
220
221          if (Enum.IsDefined(typeof(AxisDimension), selectedAxis)) {
222            AxisDimension axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), selectedAxis);
223            switch (axisDimension) {
224              case AxisDimension.Color: value = new StringValue(run.Color.ToString());
225                break;
226            }
227          } else value = Content.GetValue(run, selectedAxis);
228          string columnName = string.Empty;
229          if (value is DoubleValue || value is IntValue)
230            columnName = selectedAxis + ": ";
231          columnName += value.ToString();
232          columnNames.Add(columnName);
233        }
234      }
235      matrix.ColumnNames = columnNames;
236      matrix.RowNames = new string[] { "Count", "Minimum", "Maximum", "Average", "Median", "Standard Deviation", "Variance", "25th Percentile", "75th Percentile" };
237
238      for (int i = 0; i < seriesCache.Count; i++) {
239        Series series = seriesCache.ElementAt(i).Value;
240        double[] seriesValues = series.Points.Select(p => p.YValues[0]).OrderBy(d => d).ToArray();
241        matrix[0, i] = seriesValues.Length;
242        matrix[1, i] = seriesValues.Min();
243        matrix[2, i] = seriesValues.Max();
244        matrix[3, i] = seriesValues.Average();
245        matrix[4, i] = seriesValues.Median();
246        matrix[5, i] = seriesValues.StandardDeviation();
247        matrix[6, i] = seriesValues.Variance();
248        matrix[7, i] = seriesValues.Percentile(0.25);
249        matrix[8, i] = seriesValues.Percentile(0.75);
250      }
251      statisticsMatrixView.Content = matrix;
252    }
253
254    private Series CreateBoxPlotSeries() {
255      Series boxPlotSeries = new Series(BoxPlotSeriesName);
256      string seriesNames = string.Concat(seriesCache.Keys.Select(x => x.ToString() + ";").ToArray());
257      seriesNames = seriesNames.Remove(seriesNames.Length - 1); //delete last ; from string
258
259      boxPlotSeries.ChartArea = BoxPlotChartAreaName;
260      boxPlotSeries.ChartType = SeriesChartType.BoxPlot;
261      boxPlotSeries["BoxPlotSeries"] = seriesNames;
262      boxPlotSeries["BoxPlotShowUnusualValues"] = "true";
263      boxPlotSeries["PointWidth"] = "0.4";
264      boxPlotSeries.BackGradientStyle = System.Windows.Forms.DataVisualization.Charting.GradientStyle.VerticalCenter;
265      boxPlotSeries.BackSecondaryColor = System.Drawing.Color.FromArgb(130, 224, 64, 10);
266      boxPlotSeries.BorderColor = System.Drawing.Color.FromArgb(64, 64, 64);
267      boxPlotSeries.Color = System.Drawing.Color.FromArgb(224, 64, 10);
268
269      return boxPlotSeries;
270    }
271
272    private void AddDataPoint(IRun run) {
273      double? xValue;
274      double? yValue;
275
276      if (!xAxisComboBox.DroppedDown)
277        this.xAxisValue = (string)xAxisComboBox.SelectedItem;
278      if (!yAxisComboBox.DroppedDown)
279        this.yAxisValue = (string)yAxisComboBox.SelectedItem;
280
281      xValue = GetValue(run, this.xAxisValue);
282      yValue = GetValue(run, this.yAxisValue);
283
284      if (xValue.HasValue && yValue.HasValue) {
285        if (!this.seriesCache.ContainsKey(xValue.Value))
286          seriesCache[xValue.Value] = new Series(xValue.Value.ToString());
287
288        Series series = seriesCache[xValue.Value];
289        DataPoint point = new DataPoint(xValue.Value, yValue.Value);
290        point.Tag = run;
291        series.Points.Add(point);
292      }
293    }
294    #endregion
295
296    #region get values from run
297    private double? GetValue(IRun run, string columnName) {
298      if (run == null || string.IsNullOrEmpty(columnName))
299        return null;
300
301      if (Enum.IsDefined(typeof(AxisDimension), columnName)) {
302        AxisDimension axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), columnName);
303        return GetValue(run, axisDimension);
304      } else {
305        int columnIndex = Matrix.ColumnNames.ToList().IndexOf(columnName);
306        IItem value = Content.GetValue(run, columnIndex);
307        if (value == null)
308          return null;
309
310        DoubleValue doubleValue = value as DoubleValue;
311        IntValue intValue = value as IntValue;
312        TimeSpanValue timeSpanValue = value as TimeSpanValue;
313        double? ret = null;
314        if (doubleValue != null) {
315          if (!double.IsNaN(doubleValue.Value) && !double.IsInfinity(doubleValue.Value))
316            ret = doubleValue.Value;
317        } else if (intValue != null)
318          ret = intValue.Value;
319        else if (timeSpanValue != null) {
320          ret = timeSpanValue.Value.TotalSeconds;
321        } else
322          ret = GetCategoricalValue(columnIndex, value.ToString());
323
324        return ret;
325      }
326    }
327    private double GetCategoricalValue(int dimension, string value) {
328      if (!this.categoricalMapping.ContainsKey(dimension))
329        this.categoricalMapping[dimension] = new Dictionary<object, double>();
330      if (!this.categoricalMapping[dimension].ContainsKey(value)) {
331        if (this.categoricalMapping[dimension].Values.Count == 0)
332          this.categoricalMapping[dimension][value] = 1.0;
333        else
334          this.categoricalMapping[dimension][value] = this.categoricalMapping[dimension].Values.Max() + 1.0;
335      }
336      return this.categoricalMapping[dimension][value];
337    }
338    private double GetValue(IRun run, AxisDimension axisDimension) {
339      double value = double.NaN;
340      switch (axisDimension) {
341        case AxisDimension.Color: {
342            value = GetCategoricalValue(-1, run.Color.ToString());
343            break;
344          }
345        default: {
346            throw new ArgumentException("No handling strategy for " + axisDimension.ToString() + " is defined.");
347          }
348      }
349      return value;
350    }
351    #endregion
352
353    #region GUI events
354    private void UpdateNoRunsVisibleLabel() {
355      if (this.chart.Series.Count > 0) {
356        noRunsLabel.Visible = false;
357        showStatisticsCheckBox.Enabled = true;
358        splitContainer.Panel2Collapsed = !showStatisticsCheckBox.Checked;
359      } else {
360        noRunsLabel.Visible = true;
361        showStatisticsCheckBox.Enabled = false;
362        splitContainer.Panel2Collapsed = true;
363      }
364    }
365
366    private void AxisComboBox_SelectedIndexChanged(object sender, EventArgs e) {
367      UpdateDataPoints();
368    }
369    private void UpdateAxisLabels() {
370      Axis xAxis = this.chart.ChartAreas[BoxPlotChartAreaName].AxisX;
371      Axis yAxis = this.chart.ChartAreas[BoxPlotChartAreaName].AxisY;
372      int axisDimensionCount = Enum.GetNames(typeof(AxisDimension)).Count();
373      SetCustomAxisLabels(xAxis, xAxisComboBox.SelectedIndex - axisDimensionCount);
374      SetCustomAxisLabels(yAxis, yAxisComboBox.SelectedIndex - axisDimensionCount);
375      if (xAxisComboBox.SelectedItem != null)
376        xAxis.Title = xAxisComboBox.SelectedItem.ToString();
377      if (yAxisComboBox.SelectedItem != null)
378        yAxis.Title = yAxisComboBox.SelectedItem.ToString();
379    }
380
381    private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) {
382      this.UpdateAxisLabels();
383    }
384
385    private void SetCustomAxisLabels(Axis axis, int dimension) {
386      axis.CustomLabels.Clear();
387      if (categoricalMapping.ContainsKey(dimension)) {
388        foreach (var pair in categoricalMapping[dimension]) {
389          string labelText = pair.Key.ToString();
390          CustomLabel label = new CustomLabel();
391          label.ToolTip = labelText;
392          if (labelText.Length > 25)
393            labelText = labelText.Substring(0, 25) + " ... ";
394          label.Text = labelText;
395          label.GridTicks = GridTickTypes.TickMark;
396          label.FromPosition = pair.Value - 0.5;
397          label.ToPosition = pair.Value + 0.5;
398          axis.CustomLabels.Add(label);
399        }
400      } else if (dimension > 0 && Content.GetValue(0, dimension) is TimeSpanValue) {
401        this.chart.ChartAreas[0].RecalculateAxesScale();
402        Axis correspondingAxis = this.chart.ChartAreas[0].Axes.Where(x => x.Name == axis.Name).SingleOrDefault();
403        if (correspondingAxis == null)
404          correspondingAxis = axis;
405        for (double i = correspondingAxis.Minimum; i <= correspondingAxis.Maximum; i += correspondingAxis.LabelStyle.Interval) {
406          TimeSpan time = TimeSpan.FromSeconds(i);
407          string x = string.Format("{0:00}:{1:00}:{2:00}", (int)time.Hours, time.Minutes, time.Seconds);
408          axis.CustomLabels.Add(i - correspondingAxis.LabelStyle.Interval / 2, i + correspondingAxis.LabelStyle.Interval / 2, x);
409        }
410      } else if (chart.ChartAreas[BoxPlotChartAreaName].AxisX == axis) {
411        double position = 1.0;
412        foreach (Series series in chart.Series) {
413          if (series.Name != BoxPlotSeriesName) {
414            string labelText = series.Points[0].XValue.ToString();
415            CustomLabel label = new CustomLabel();
416            label.FromPosition = position - 0.5;
417            label.ToPosition = position + 0.5;
418            label.GridTicks = GridTickTypes.TickMark;
419            label.Text = labelText;
420            axis.CustomLabels.Add(label);
421            position++;
422          }
423        }
424      }
425    }
426
427    private void chart_MouseMove(object sender, MouseEventArgs e) {
428      string newTooltipText = string.Empty;
429      string oldTooltipText;
430      HitTestResult h = this.chart.HitTest(e.X, e.Y);
431      if (h.ChartElementType == ChartElementType.AxisLabels) {
432        newTooltipText = ((CustomLabel)h.Object).ToolTip;
433      }
434
435      oldTooltipText = this.tooltip.GetToolTip(chart);
436      if (newTooltipText != oldTooltipText)
437        this.tooltip.SetToolTip(chart, newTooltipText);
438    }
439    #endregion
440
441    private void showStatisticsCheckBox_CheckedChanged(object sender, EventArgs e) {
442      splitContainer.Panel2Collapsed = !showStatisticsCheckBox.Checked;
443    }
444
445  }
446}
Note: See TracBrowser for help on using the repository browser.