Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 3718 was 3707, checked in by gkronber, 14 years ago

Added adjustment of cursor interval based on range of displayed values in the RunCollectionBubbleChartView ScatterPlotView and LineChartView. #893

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