Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 4774 was 4768, checked in by mkommend, 14 years ago

Added Minimum and Maximum to BoxPlot statistics (ticket #1262).

File size: 17.0 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(9, 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[] { "Count", "Minimum", "Maximum", "Average", "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.Length;
215        matrix[1, i] = seriesValues.Min();
216        matrix[2, i] = seriesValues.Max();
217        matrix[3, i] = seriesValues.Average();
218        matrix[4, i] = seriesValues.Median();
219        matrix[5, i] = seriesValues.StandardDeviation();
220        matrix[6, i] = seriesValues.Variance();
221        matrix[7, i] = seriesValues.Percentile(0.25);
222        matrix[8, i] = seriesValues.Percentile(0.75);
223      }
224      statisticsMatrixView.Content = matrix;
225    }
226
227    private Series CreateBoxPlotSeries() {
228      Series boxPlotSeries = new Series(BoxPlotSeriesName);
229      string seriesNames = string.Concat(seriesCache.Keys.Select(x => x.ToString() + ";").ToArray());
230      seriesNames = seriesNames.Remove(seriesNames.Length - 1); //delete last ; from string
231
232      boxPlotSeries.ChartArea = BoxPlotChartAreaName;
233      boxPlotSeries.ChartType = SeriesChartType.BoxPlot;
234      boxPlotSeries["BoxPlotSeries"] = seriesNames;
235      boxPlotSeries["BoxPlotShowUnusualValues"] = "true";
236      boxPlotSeries["PointWidth"] = "0.4";
237      boxPlotSeries.BackGradientStyle = System.Windows.Forms.DataVisualization.Charting.GradientStyle.VerticalCenter;
238      boxPlotSeries.BackSecondaryColor = System.Drawing.Color.FromArgb(130, 224, 64, 10);
239      boxPlotSeries.BorderColor = System.Drawing.Color.FromArgb(64, 64, 64);
240      boxPlotSeries.Color = System.Drawing.Color.FromArgb(224, 64, 10);
241
242      return boxPlotSeries;
243    }
244
245    private void AddDataPoint(IRun run) {
246      double? xValue;
247      double? yValue;
248
249      if (!xAxisComboBox.DroppedDown)
250        this.xAxisValue = (string)xAxisComboBox.SelectedItem;
251      if (!yAxisComboBox.DroppedDown)
252        this.yAxisValue = (string)yAxisComboBox.SelectedItem;
253
254      xValue = GetValue(run, this.xAxisValue);
255      yValue = GetValue(run, this.yAxisValue);
256
257      if (xValue.HasValue && yValue.HasValue) {
258        if (!this.seriesCache.ContainsKey(xValue.Value))
259          seriesCache[xValue.Value] = new Series(xValue.Value.ToString());
260
261        Series series = seriesCache[xValue.Value];
262        DataPoint point = new DataPoint(xValue.Value, yValue.Value);
263        point.Tag = run;
264        series.Points.Add(point);
265      }
266    }
267    #endregion
268
269    #region get values from run
270    private double? GetValue(IRun run, string columnName) {
271      if (run == null || string.IsNullOrEmpty(columnName))
272        return null;
273
274      if (Enum.IsDefined(typeof(AxisDimension), columnName)) {
275        AxisDimension axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), columnName);
276        return GetValue(run, axisDimension);
277      } else {
278        int columnIndex = Matrix.ColumnNames.ToList().IndexOf(columnName);
279        IItem value = Content.GetValue(run, columnIndex);
280        if (value == null)
281          return null;
282
283        DoubleValue doubleValue = value as DoubleValue;
284        IntValue intValue = value as IntValue;
285        TimeSpanValue timeSpanValue = value as TimeSpanValue;
286        double? ret = null;
287        if (doubleValue != null) {
288          if (!double.IsNaN(doubleValue.Value) && !double.IsInfinity(doubleValue.Value))
289            ret = doubleValue.Value;
290        } else if (intValue != null)
291          ret = intValue.Value;
292        else if (timeSpanValue != null) {
293          ret = timeSpanValue.Value.TotalSeconds;
294        } else
295          ret = GetCategoricalValue(columnIndex, value.ToString());
296
297        return ret;
298      }
299    }
300    private double GetCategoricalValue(int dimension, string value) {
301      if (!this.categoricalMapping.ContainsKey(dimension))
302        this.categoricalMapping[dimension] = new Dictionary<object, double>();
303      if (!this.categoricalMapping[dimension].ContainsKey(value)) {
304        if (this.categoricalMapping[dimension].Values.Count == 0)
305          this.categoricalMapping[dimension][value] = 1.0;
306        else
307          this.categoricalMapping[dimension][value] = this.categoricalMapping[dimension].Values.Max() + 1.0;
308      }
309      return this.categoricalMapping[dimension][value];
310    }
311    private double GetValue(IRun run, AxisDimension axisDimension) {
312      double value = double.NaN;
313      switch (axisDimension) {
314        case AxisDimension.Color: {
315            value = GetCategoricalValue(-1, run.Color.ToString());
316            break;
317          }
318        default: {
319            throw new ArgumentException("No handling strategy for " + axisDimension.ToString() + " is defined.");
320          }
321      }
322      return value;
323    }
324    #endregion
325
326    #region GUI events
327    private void UpdateNoRunsVisibleLabel() {
328      if (this.chart.Series.Count > 0)
329        noRunsLabel.Visible = false;
330      else
331        noRunsLabel.Visible = true;
332    }
333
334    private void AxisComboBox_SelectedIndexChanged(object sender, EventArgs e) {
335      UpdateDataPoints();
336    }
337    private void UpdateAxisLabels() {
338      Axis xAxis = this.chart.ChartAreas[BoxPlotChartAreaName].AxisX;
339      Axis yAxis = this.chart.ChartAreas[BoxPlotChartAreaName].AxisY;
340      int axisDimensionCount = Enum.GetNames(typeof(AxisDimension)).Count();
341      SetCustomAxisLabels(xAxis, xAxisComboBox.SelectedIndex - axisDimensionCount);
342      SetCustomAxisLabels(yAxis, yAxisComboBox.SelectedIndex - axisDimensionCount);
343      if (xAxisComboBox.SelectedItem != null)
344        xAxis.Title = xAxisComboBox.SelectedItem.ToString();
345      if (yAxisComboBox.SelectedItem != null)
346        yAxis.Title = yAxisComboBox.SelectedItem.ToString();
347    }
348
349    private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) {
350      this.UpdateAxisLabels();
351    }
352
353    private void SetCustomAxisLabels(Axis axis, int dimension) {
354      axis.CustomLabels.Clear();
355      if (categoricalMapping.ContainsKey(dimension)) {
356        foreach (var pair in categoricalMapping[dimension]) {
357          string labelText = pair.Key.ToString();
358          CustomLabel label = new CustomLabel();
359          label.ToolTip = labelText;
360          if (labelText.Length > 25)
361            labelText = labelText.Substring(0, 25) + " ... ";
362          label.Text = labelText;
363          label.GridTicks = GridTickTypes.TickMark;
364          label.FromPosition = pair.Value - 0.5;
365          label.ToPosition = pair.Value + 0.5;
366          axis.CustomLabels.Add(label);
367        }
368      } else if (dimension > 0 && Content.GetValue(0, dimension) is TimeSpanValue) {
369        this.chart.ChartAreas[0].RecalculateAxesScale();
370        Axis correspondingAxis = this.chart.ChartAreas[0].Axes.Where(x => x.Name == axis.Name).SingleOrDefault();
371        if (correspondingAxis == null)
372          correspondingAxis = axis;
373        for (double i = correspondingAxis.Minimum; i <= correspondingAxis.Maximum; i += correspondingAxis.LabelStyle.Interval) {
374          TimeSpan time = TimeSpan.FromSeconds(i);
375          string x = string.Format("{0:00}:{1:00}:{2:00}", (int)time.Hours, time.Minutes, time.Seconds);
376          axis.CustomLabels.Add(i - correspondingAxis.LabelStyle.Interval / 2, i + correspondingAxis.LabelStyle.Interval / 2, x);
377        }
378      } else if (chart.ChartAreas[BoxPlotChartAreaName].AxisX == axis) {
379        double position = 1.0;
380        foreach (Series series in chart.Series) {
381          if (series.Name != BoxPlotSeriesName) {
382            string labelText = series.Points[0].XValue.ToString();
383            CustomLabel label = new CustomLabel();
384            label.FromPosition = position - 0.5;
385            label.ToPosition = position + 0.5;
386            label.GridTicks = GridTickTypes.TickMark;
387            label.Text = labelText;
388            axis.CustomLabels.Add(label);
389            position++;
390          }
391        }
392      }
393    }
394
395    private void chart_MouseMove(object sender, MouseEventArgs e) {
396      string newTooltipText = string.Empty;
397      string oldTooltipText;
398      HitTestResult h = this.chart.HitTest(e.X, e.Y);
399      if (h.ChartElementType == ChartElementType.AxisLabels) {
400        newTooltipText = ((CustomLabel)h.Object).ToolTip;
401      }
402
403      oldTooltipText = this.tooltip.GetToolTip(chart);
404      if (newTooltipText != oldTooltipText)
405        this.tooltip.SetToolTip(chart, newTooltipText);
406    }
407    #endregion
408
409    private void showStatisticsCheckBox_CheckedChanged(object sender, EventArgs e) {
410      splitContainer.Panel2Collapsed = !showStatisticsCheckBox.Checked;
411    }
412
413  }
414}
Note: See TracBrowser for help on using the repository browser.