source: trunk/sources/HeuristicLab.Optimization.Views/3.3/RunCollectionBubbleChartView.cs @ 3543

Last change on this file since 3543 was 3543, checked in by mkommend, 12 years ago

updated RunCollectionBubbleChart (ticket #970)

File size: 19.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.ComponentModel;
25using System.Data;
26using System.Drawing;
27using System.Linq;
28using System.Text;
29using System.Windows.Forms;
30using HeuristicLab.MainForm.WindowsForms;
31using HeuristicLab.MainForm;
32using System.Windows.Forms.DataVisualization.Charting;
33using HeuristicLab.Common;
34using HeuristicLab.Core;
35using HeuristicLab.Data;
36using System.Threading;
37
38namespace HeuristicLab.Optimization.Views {
39  [View("RunCollection BubbleChart")]
40  [Content(typeof(RunCollection), false)]
41  public partial class RunCollectionBubbleChartView : AsynchronousContentView {
42    private enum SizeDimension { Constant = 0 }
43    private enum AxisDimension { Index = 0 }
44
45    private Dictionary<int, Dictionary<object, double>> categoricalMapping;
46    private Dictionary<IRun, double> xJitter;
47    private Dictionary<IRun, double> yJitter;
48    private double xJitterFactor = 0.0;
49    private double yJitterFactor = 0.0;
50    private Random random;
51    private bool isSelecting = false;
52
53    public RunCollectionBubbleChartView() {
54      InitializeComponent();
55      Caption = "Run Collection Bubble Chart";
56
57      this.categoricalMapping = new Dictionary<int, Dictionary<object, double>>();
58      this.xJitter = new Dictionary<IRun, double>();
59      this.yJitter = new Dictionary<IRun, double>();
60      this.random = new Random();
61      this.colorDialog.Color = Color.Black;
62      this.colorButton.Image = this.GenerateImage(16, 16, this.colorDialog.Color);
63      this.isSelecting = false;
64
65     
66      this.chart.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
67      this.chart.ChartAreas[0].CursorY.IsUserSelectionEnabled = true;
68      this.chart.ChartAreas[0].CursorX.Interval = 0;
69      this.chart.ChartAreas[0].CursorY.Interval = 0;
70      this.chart.ChartAreas[0].AxisX.ScaleView.Zoomable = !this.isSelecting;
71      this.chart.ChartAreas[0].AxisX.IntervalAutoMode = IntervalAutoMode.VariableCount;
72      this.chart.ChartAreas[0].AxisY.ScaleView.Zoomable = !this.isSelecting;
73      this.chart.ChartAreas[0].AxisY.IntervalAutoMode = IntervalAutoMode.VariableCount;
74    }
75
76    public RunCollectionBubbleChartView(RunCollection content)
77      : this() {
78      Content = content;
79    }
80
81    public new RunCollection Content {
82      get { return (RunCollection)base.Content; }
83      set { base.Content = value; }
84    }
85
86    public IStringConvertibleMatrix Matrix {
87      get { return this.Content; }
88    }
89
90    protected override void RegisterContentEvents() {
91      base.RegisterContentEvents();
92      Content.Reset += new EventHandler(Content_Reset);
93      Content.ColumnNamesChanged += new EventHandler(Content_ColumnNamesChanged);
94      Content.ItemsAdded += new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsAdded);
95      Content.ItemsRemoved += new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsRemoved);
96      Content.CollectionReset += new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_CollectionReset);
97      RegisterRunEvents(Content);
98    }
99    protected virtual void RegisterRunEvents(IEnumerable<IRun> runs) {
100      foreach (IRun run in runs)
101        run.Changed += new EventHandler(run_Changed);
102    }
103    protected override void DeregisterContentEvents() {
104      base.DeregisterContentEvents();
105      Content.Reset -= new EventHandler(Content_Reset);
106      Content.ColumnNamesChanged -= new EventHandler(Content_ColumnNamesChanged);
107      Content.ItemsAdded -= new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsAdded);
108      Content.ItemsRemoved -= new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsRemoved);
109      Content.CollectionReset -= new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_CollectionReset);
110      DeregisterRunEvents(Content);
111    }
112    protected virtual void DeregisterRunEvents(IEnumerable<IRun> runs) {
113      foreach (IRun run in runs)
114        run.Changed -= new EventHandler(run_Changed);
115    }
116
117    private void Content_CollectionReset(object sender, HeuristicLab.Collections.CollectionItemsChangedEventArgs<IRun> e) {
118      DeregisterRunEvents(e.OldItems);
119      RegisterRunEvents(e.Items);
120    }
121    private void Content_ItemsRemoved(object sender, HeuristicLab.Collections.CollectionItemsChangedEventArgs<IRun> e) {
122      DeregisterRunEvents(e.Items);
123    }
124    private void Content_ItemsAdded(object sender, HeuristicLab.Collections.CollectionItemsChangedEventArgs<IRun> e) {
125      RegisterRunEvents(e.Items);
126    }
127    private void run_Changed(object sender, EventArgs e) {
128      IRun run = (IRun)sender;
129      DataPoint point = this.chart.Series[0].Points.Where(p => p.Tag == run).SingleOrDefault();
130      if (point != null) {
131        point.Color = run.Color;
132        if (!run.Visible)
133          this.chart.Series[0].Points.Remove(point);
134      } else
135        AddDataPoint(run);
136    }
137
138    protected override void OnContentChanged() {
139      base.OnContentChanged();
140      this.categoricalMapping.Clear();
141      UpdateComboBoxes();
142      UpdateDataPoints();
143    }
144    private void Content_ColumnNamesChanged(object sender, EventArgs e) {
145      if (InvokeRequired)
146        Invoke(new EventHandler(Content_ColumnNamesChanged), sender, e);
147      else
148        UpdateComboBoxes();
149    }
150
151    private void UpdateComboBoxes() {
152      this.xAxisComboBox.Items.Clear();
153      this.yAxisComboBox.Items.Clear();
154      this.sizeComboBox.Items.Clear();
155      if (Content != null) {
156        string[] additionalAxisDimension = Enum.GetNames(typeof(AxisDimension));
157        this.xAxisComboBox.Items.AddRange(additionalAxisDimension);
158        this.xAxisComboBox.Items.AddRange(Matrix.ColumnNames.ToArray());
159        this.yAxisComboBox.Items.AddRange(additionalAxisDimension);
160        this.yAxisComboBox.Items.AddRange(Matrix.ColumnNames.ToArray());
161        string[] additionalSizeDimension = Enum.GetNames(typeof(SizeDimension));
162        this.sizeComboBox.Items.AddRange(additionalSizeDimension);
163        this.sizeComboBox.Items.AddRange(Matrix.ColumnNames.ToArray());
164        this.sizeComboBox.SelectedItem = SizeDimension.Constant.ToString();
165      }
166    }
167
168    private void Content_Reset(object sender, EventArgs e) {
169      if (InvokeRequired)
170        Invoke(new EventHandler(Content_Reset), sender, e);
171      else {
172        this.categoricalMapping.Clear();
173        UpdateDataPoints();
174      }
175    }
176
177    private void UpdateDataPoints() {
178      Series series = this.chart.Series[0];
179      series.Points.Clear();
180      if (Content != null) {
181        foreach (IRun run in this.Content)
182          this.AddDataPoint(run);
183
184        //check to correct max bubble size
185        if (this.chart.Series[0].Points.Select(p => p.YValues[1]).Distinct().Count() == 1)
186          this.chart.Series[0]["BubbleMaxSize"] = "2";
187        else
188          this.chart.Series[0]["BubbleMaxSize"] = "7";
189
190        if (this.chart.Series[0].Points.Count == 0)
191          noRunsLabel.Visible = true;
192        else
193          noRunsLabel.Visible = false;
194      }
195    }
196    private void AddDataPoint(IRun run) {
197      double? xValue;
198      double? yValue;
199      double? sizeValue;
200      Series series = this.chart.Series[0];
201      int row = this.Content.ToList().IndexOf(run);
202      xValue = GetValue(run, (string)xAxisComboBox.SelectedItem);
203      yValue = GetValue(run, (string)yAxisComboBox.SelectedItem);
204      sizeValue = GetValue(run, (string)sizeComboBox.SelectedItem);
205      if (xValue.HasValue && yValue.HasValue && sizeValue.HasValue) {
206        xValue = xValue.Value + xValue.Value * GetXJitter(run) * xJitterFactor;
207        yValue = yValue.Value + yValue.Value * GetYJitter(run) * yJitterFactor;
208        if (run.Visible) {
209          DataPoint point = new DataPoint(xValue.Value, new double[] { yValue.Value, sizeValue.Value });
210          point.Tag = run;
211          point.Color = run.Color;
212          series.Points.Add(point);
213        }
214      }
215    }
216    private double? GetValue(IRun run, string columnName) {
217      if (run == null || string.IsNullOrEmpty(columnName))
218        return null;
219
220      if (Enum.IsDefined(typeof(AxisDimension), columnName)) {
221        AxisDimension axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), columnName);
222        return GetValue(run, axisDimension);
223      } else if (Enum.IsDefined(typeof(SizeDimension), columnName)) {
224        SizeDimension sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), columnName);
225        return GetValue(run, sizeDimension);
226      } else {
227        int columnIndex = Matrix.ColumnNames.ToList().IndexOf(columnName);
228        IItem value = Content.GetValue(run, columnIndex);
229        if (value == null)
230          return null;
231
232        DoubleValue doubleValue = value as DoubleValue;
233        IntValue intValue = value as IntValue;
234        double? ret = null;
235        if (doubleValue != null) {
236          if (!double.IsNaN(doubleValue.Value) && !double.IsInfinity(doubleValue.Value))
237            ret = doubleValue.Value;
238        } else if (intValue != null)
239          ret = intValue.Value;
240        else
241          ret = GetCategoricalValue(columnIndex, value.ToString());
242
243        return ret;
244      }
245    }
246    private double GetCategoricalValue(int dimension, string value) {
247      if (!this.categoricalMapping.ContainsKey(dimension))
248        this.categoricalMapping[dimension] = new Dictionary<object, double>();
249      if (!this.categoricalMapping[dimension].ContainsKey(value)) {
250        if (this.categoricalMapping[dimension].Values.Count == 0)
251          this.categoricalMapping[dimension][value] = 1.0;
252        else
253          this.categoricalMapping[dimension][value] = this.categoricalMapping[dimension].Values.Max() + 1.0;
254      }
255      return this.categoricalMapping[dimension][value];
256    }
257    private double GetValue(IRun run, AxisDimension axisDimension) {
258      double value = double.NaN;
259      switch (axisDimension) {
260        case AxisDimension.Index: {
261            value = Content.ToList().IndexOf(run);
262            break;
263          }
264        default: {
265            throw new ArgumentException("No handling strategy for " + axisDimension.ToString() + " is defined.");
266          }
267      }
268      return value;
269    }
270    private double GetValue(IRun run, SizeDimension sizeDimension) {
271      double value = double.NaN;
272      switch (sizeDimension) {
273        case SizeDimension.Constant: {
274            value = 2;
275            break;
276          }
277        default: {
278            throw new ArgumentException("No handling strategy for " + sizeDimension.ToString() + " is defined.");
279          }
280      }
281      return value;
282    }
283
284    #region drag and drop and tooltip
285    private IRun draggedRun;
286    private void chart_MouseDown(object sender, MouseEventArgs e) {
287      HitTestResult h = this.chart.HitTest(e.X, e.Y);
288      if (h.ChartElementType == ChartElementType.DataPoint) {
289        IRun run = (IRun)((DataPoint)h.Object).Tag;
290        if (e.Clicks >= 2) {
291          IContentView view = MainFormManager.CreateDefaultView(run);
292          view.ReadOnly = this.ReadOnly;
293          view.Locked = this.Locked;
294          view.Show();
295        } else {
296          this.draggedRun = run;
297          this.chart.ChartAreas[0].CursorX.SetSelectionPosition(double.NaN, double.NaN);
298          this.chart.ChartAreas[0].CursorY.SetSelectionPosition(double.NaN, double.NaN);
299        }
300      }
301    }
302
303    private void chart_MouseUp(object sender, MouseEventArgs e) {
304      if (isSelecting) {
305        System.Windows.Forms.DataVisualization.Charting.Cursor xCursor = chart.ChartAreas[0].CursorX;
306        System.Windows.Forms.DataVisualization.Charting.Cursor yCursor = chart.ChartAreas[0].CursorY;
307
308        double minX = Math.Min(xCursor.SelectionStart, xCursor.SelectionEnd);
309        double maxX = Math.Max(xCursor.SelectionStart, xCursor.SelectionEnd);
310        double minY = Math.Min(yCursor.SelectionStart, yCursor.SelectionEnd);
311        double maxY = Math.Max(yCursor.SelectionStart, yCursor.SelectionEnd);
312
313        //check for click to select model
314        if (minX == maxX && minY == maxY) {
315          HitTestResult hitTest = chart.HitTest(e.X, e.Y);
316          if (hitTest.ChartElementType == ChartElementType.DataPoint) {
317            int pointIndex = hitTest.PointIndex;
318            IRun run = (IRun)this.chart.Series[0].Points[pointIndex].Tag;
319            run.Color = colorDialog.Color;
320          }
321        } else {
322          List<DataPoint> selectedPoints = new List<DataPoint>();
323          foreach (DataPoint p in this.chart.Series[0].Points) {
324            if (p.XValue >= minX && p.XValue < maxX &&
325              p.YValues[0] >= minY && p.YValues[0] < maxY) {
326              selectedPoints.Add(p);
327            }
328          }
329          foreach (DataPoint p in selectedPoints) {
330            IRun run = (IRun)p.Tag;
331            run.Color = colorDialog.Color;
332          }
333        }
334        this.chart.ChartAreas[0].CursorX.SetSelectionPosition(double.NaN, double.NaN);
335        this.chart.ChartAreas[0].CursorY.SetSelectionPosition(double.NaN, double.NaN);
336      }
337    }
338
339    private void chart_MouseMove(object sender, MouseEventArgs e) {
340      HitTestResult h = this.chart.HitTest(e.X, e.Y);
341      if (!Locked) {
342        if (this.draggedRun != null && h.ChartElementType != ChartElementType.DataPoint) {
343          DataObject data = new DataObject();
344          data.SetData("Type", draggedRun.GetType());
345          data.SetData("Value", draggedRun);
346          if (ReadOnly)
347            DoDragDrop(data, DragDropEffects.Copy | DragDropEffects.Link);
348          else {
349            DragDropEffects result = DoDragDrop(data, DragDropEffects.Copy | DragDropEffects.Link | DragDropEffects.Move);
350            if ((result & DragDropEffects.Move) == DragDropEffects.Move)
351              Content.Remove(draggedRun);
352          }
353          this.chart.ChartAreas[0].AxisX.ScaleView.Zoomable = !isSelecting;
354          this.chart.ChartAreas[0].AxisY.ScaleView.Zoomable = !isSelecting;
355          this.draggedRun = null;
356        }
357      }
358      string newTooltipText = string.Empty;
359      string oldTooltipText;
360      if (h.ChartElementType == ChartElementType.DataPoint) {
361        IRun run = (IRun)((DataPoint)h.Object).Tag;
362        newTooltipText = BuildTooltip(run);
363      }
364
365      oldTooltipText = this.tooltip.GetToolTip(chart);
366      if (newTooltipText != oldTooltipText)
367        this.tooltip.SetToolTip(chart, newTooltipText);
368    }
369
370    private string BuildTooltip(IRun run) {
371      string tooltip;
372      tooltip = run.Name + System.Environment.NewLine;
373
374      double? xValue = this.GetValue(run, (string)xAxisComboBox.SelectedItem);
375      double? yValue = this.GetValue(run, (string)yAxisComboBox.SelectedItem);
376      double? sizeValue = this.GetValue(run, (string)sizeComboBox.SelectedItem);
377
378      string xString = xValue == null ? string.Empty : xValue.Value.ToString();
379      string yString = yValue == null ? string.Empty : yValue.Value.ToString();
380      string sizeString = sizeValue == null ? string.Empty : sizeValue.Value.ToString();
381
382      tooltip += xAxisComboBox.SelectedItem + " : " + xString + Environment.NewLine;
383      tooltip += yAxisComboBox.SelectedItem + " : " + yString + Environment.NewLine;
384      tooltip += sizeComboBox.SelectedItem + " : " + sizeString + Environment.NewLine;
385
386      return tooltip;
387    }
388    #endregion
389
390    #region GUI events and updating
391    private double GetXJitter(IRun run) {
392      if (!this.xJitter.ContainsKey(run))
393        this.xJitter[run] = random.NextDouble() * 2.0 - 1.0;
394      return this.xJitter[run];
395    }
396    private double GetYJitter(IRun run) {
397      if (!this.yJitter.ContainsKey(run))
398        this.yJitter[run] = random.NextDouble() * 2.0 - 1.0;
399      return this.yJitter[run];
400    }
401    private void jitterTrackBar_ValueChanged(object sender, EventArgs e) {
402      this.xJitterFactor = xTrackBar.Value / 100.0;
403      this.yJitterFactor = yTrackBar.Value / 100.0;
404      this.UpdateDataPoints();
405    }
406
407    private void AxisComboBox_SelectedIndexChanged(object sender, EventArgs e) {
408      UpdateDataPoints();
409      UpdateAxisLabels();
410    }
411    private void UpdateAxisLabels() {
412      Axis xAxis = this.chart.ChartAreas[0].AxisX;
413      Axis yAxis = this.chart.ChartAreas[0].AxisY;
414      int axisDimensionCount = Enum.GetNames(typeof(AxisDimension)).Count();
415      SetCustomAxisLabels(xAxis, xAxisComboBox.SelectedIndex - axisDimensionCount);
416      SetCustomAxisLabels(yAxis, yAxisComboBox.SelectedIndex - axisDimensionCount);
417    }
418    private void SetCustomAxisLabels(Axis axis, int dimension) {
419      axis.CustomLabels.Clear();
420      if (categoricalMapping.ContainsKey(dimension)) {
421        CustomLabel label = null;
422        foreach (var pair in categoricalMapping[dimension]) {
423          string labelText = pair.Key.ToString();
424          if (labelText.Length > 25)
425            labelText = labelText.Substring(0, 25) + " ... ";
426          label = axis.CustomLabels.Add(pair.Value - 0.5, pair.Value + 0.5, labelText);
427          label.GridTicks = GridTickTypes.TickMark;
428        }
429        axis.IsLabelAutoFit = false;
430        axis.LabelStyle.Enabled = true;
431        axis.LabelStyle.Angle = 0;
432        axis.LabelStyle.TruncatedLabels = true;
433      }
434    }
435
436    private void zoomButton_CheckedChanged(object sender, EventArgs e) {
437      this.isSelecting = selectButton.Checked;
438      this.colorButton.Enabled = this.isSelecting;
439      this.chart.ChartAreas[0].AxisX.ScaleView.Zoomable = !isSelecting;
440      this.chart.ChartAreas[0].AxisY.ScaleView.Zoomable = !isSelecting;
441    }
442    private void colorButton_Click(object sender, EventArgs e) {
443      if (colorDialog.ShowDialog(this) == DialogResult.OK) {
444        this.colorButton.Image = this.GenerateImage(16, 16, this.colorDialog.Color);
445      }
446    }
447    private Image GenerateImage(int width, int height, Color fillColor) {
448      Image colorImage = new Bitmap(width, height);
449      using (Graphics gfx = Graphics.FromImage(colorImage)) {
450        using (SolidBrush brush = new SolidBrush(fillColor)) {
451          gfx.FillRectangle(brush, 0, 0, width, height);
452        }
453      }
454      return colorImage;
455    }
456    #endregion
457  }
458}
Note: See TracBrowser for help on using the repository browser.