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

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

corrected axis labels in RunCollectionBubbleChartView; this also avoids GDI exceptions (ticket #970)

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