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

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

#1710: Organized optimization views into folder and improved the automatic coloring of runs.

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