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

Last change on this file since 4652 was 4652, checked in by mkommend, 11 years ago

Added statistical information to the RunCollectionBoxPlotView and corrected minor bugs in the 'RunCollectionBubbleChartView' and the 'RunCollectionBoxPlotView' (ticket #1135).

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