Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2016: Changes in the bubblechart:

  • Added possibility to select runs.
  • Added option to hide all selected runs.
  • Performance improvements regarding coloring, filtering and selection.
  • Corrected minor bug regarding categorial values in the bubblechart and boxplot view.
File size: 34.5 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2012 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.Drawing;
25using System.Linq;
26using System.Windows.Forms;
27using System.Windows.Forms.DataVisualization.Charting;
28using HeuristicLab.Common;
29using HeuristicLab.Core;
30using HeuristicLab.Data;
31using HeuristicLab.MainForm;
32using HeuristicLab.MainForm.WindowsForms;
33
34namespace HeuristicLab.Optimization.Views {
35  [View("RunCollection BubbleChart")]
36  [Content(typeof(RunCollection), false)]
37  public partial class RunCollectionBubbleChartView : AsynchronousContentView {
38    private enum SizeDimension { Constant = 0 }
39    private enum AxisDimension { Index = 0 }
40
41    private string xAxisValue;
42    private string yAxisValue;
43    private string sizeAxisValue;
44
45    private readonly Dictionary<IRun, List<DataPoint>> runToDataPointMapping = new Dictionary<IRun, List<DataPoint>>();
46    private readonly Dictionary<IRun, int> runToIndexMapping = new Dictionary<IRun, int>();
47    private readonly Dictionary<int, Dictionary<object, double>> categoricalMapping = new Dictionary<int, Dictionary<object, double>>();
48    private readonly Dictionary<IRun, double> xJitter = new Dictionary<IRun, double>();
49    private readonly Dictionary<IRun, double> yJitter = new Dictionary<IRun, double>();
50
51    private readonly HashSet<IRun> selectedRuns = new HashSet<IRun>();
52    private readonly Random random = new Random();
53    private double xJitterFactor = 0.0;
54    private double yJitterFactor = 0.0;
55    private bool isSelecting = false;
56    private bool suppressUpdates = false;
57
58
59    public RunCollectionBubbleChartView() {
60      InitializeComponent();
61
62      chart.ContextMenuStrip.Items.Insert(0, hideRunToolStripMenuItem);
63      chart.ContextMenuStrip.Items.Insert(1, openBoxPlotViewToolStripMenuItem);
64      chart.ContextMenuStrip.Items.Add(getDataAsMatrixToolStripMenuItem);
65      chart.ContextMenuStrip.Opening += new System.ComponentModel.CancelEventHandler(ContextMenuStrip_Opening);
66
67      colorDialog.Color = Color.Black;
68      colorDialogButton.Image = this.GenerateImage(16, 16, this.colorDialog.Color);
69      isSelecting = false;
70
71      chart.CustomizeAllChartAreas();
72      chart.ChartAreas[0].CursorX.Interval = 1;
73      chart.ChartAreas[0].CursorY.Interval = 1;
74      chart.ChartAreas[0].AxisX.ScaleView.Zoomable = !this.isSelecting;
75      chart.ChartAreas[0].AxisY.ScaleView.Zoomable = !this.isSelecting;
76    }
77
78    public new RunCollection Content {
79      get { return (RunCollection)base.Content; }
80      set { base.Content = value; }
81    }
82    public IStringConvertibleMatrix Matrix {
83      get { return this.Content; }
84    }
85    public IEnumerable<IRun> SelectedRuns {
86      get { return selectedRuns; }
87    }
88
89    protected override void RegisterContentEvents() {
90      base.RegisterContentEvents();
91      Content.Reset += new EventHandler(Content_Reset);
92      Content.ColumnNamesChanged += new EventHandler(Content_ColumnNamesChanged);
93      Content.ItemsAdded += new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsAdded);
94      Content.ItemsRemoved += new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsRemoved);
95      Content.CollectionReset += new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_CollectionReset);
96      Content.OptimizerNameChanged += new EventHandler(Content_AlgorithmNameChanged);
97      Content.UpdateOfRunsInProgressChanged += new EventHandler(Content_UpdateOfRunsInProgressChanged);
98      RegisterRunEvents(Content);
99    }
100    protected override void DeregisterContentEvents() {
101      base.DeregisterContentEvents();
102      Content.Reset -= new EventHandler(Content_Reset);
103      Content.ColumnNamesChanged -= new EventHandler(Content_ColumnNamesChanged);
104      Content.ItemsAdded -= new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsAdded);
105      Content.ItemsRemoved -= new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_ItemsRemoved);
106      Content.CollectionReset -= new HeuristicLab.Collections.CollectionItemsChangedEventHandler<IRun>(Content_CollectionReset);
107      Content.OptimizerNameChanged -= new EventHandler(Content_AlgorithmNameChanged);
108      Content.UpdateOfRunsInProgressChanged -= new EventHandler(Content_UpdateOfRunsInProgressChanged);
109      DeregisterRunEvents(Content);
110    }
111    protected virtual void RegisterRunEvents(IEnumerable<IRun> runs) {
112      foreach (IRun run in runs)
113        run.Changed += new EventHandler(run_Changed);
114    }
115    protected virtual void DeregisterRunEvents(IEnumerable<IRun> runs) {
116      foreach (IRun run in runs)
117        run.Changed -= new EventHandler(run_Changed);
118    }
119
120    private void Content_CollectionReset(object sender, HeuristicLab.Collections.CollectionItemsChangedEventArgs<IRun> e) {
121      DeregisterRunEvents(e.OldItems);
122      RegisterRunEvents(e.Items);
123    }
124    private void Content_ItemsRemoved(object sender, HeuristicLab.Collections.CollectionItemsChangedEventArgs<IRun> e) {
125      DeregisterRunEvents(e.Items);
126    }
127    private void Content_ItemsAdded(object sender, HeuristicLab.Collections.CollectionItemsChangedEventArgs<IRun> e) {
128      RegisterRunEvents(e.Items);
129    }
130    private void run_Changed(object sender, EventArgs e) {
131      if (suppressUpdates) return;
132      if (InvokeRequired)
133        this.Invoke(new EventHandler(run_Changed), sender, e);
134      else {
135        IRun run = (IRun)sender;
136        UpdateRun(run);
137        UpdateCursorInterval();
138        chart.ChartAreas[0].RecalculateAxesScale();
139        UpdateAxisLabels();
140      }
141    }
142
143    private void Content_UpdateOfRunsInProgressChanged(object sender, EventArgs e) {
144      if (InvokeRequired)
145        this.Invoke(new EventHandler(Content_UpdateOfRunsInProgressChanged), sender, e);
146      else {
147        suppressUpdates = Content.UpdateOfRunsInProgress;
148        if (suppressUpdates) return;
149
150        foreach (var run in Content) UpdateRun(run);
151        UpdateMarkerSizes();
152        UpdateCursorInterval();
153        chart.ChartAreas[0].RecalculateAxesScale();
154        UpdateAxisLabels();
155      }
156    }
157
158    private void UpdateRun(IRun run) {
159      if (runToDataPointMapping.ContainsKey(run)) {
160        foreach (DataPoint point in runToDataPointMapping[run]) {
161          if (!run.Visible) {
162            this.chart.Series[0].Points.Remove(point);
163            continue;
164          }
165          if (selectedRuns.Contains(run)) {
166            point.Color = Color.Red;
167            point.MarkerStyle = MarkerStyle.Cross;
168          } else {
169            point.Color = Color.FromArgb(255 - transparencyTrackBar.Value, ((IRun)point.Tag).Color);
170            point.MarkerStyle = MarkerStyle.Circle;
171          }
172
173        }
174        if (!run.Visible) runToDataPointMapping.Remove(run);
175      } else {
176        AddDataPoint(run);
177      }
178
179      if (this.chart.Series[0].Points.Count == 0)
180        noRunsLabel.Visible = true;
181      else
182        noRunsLabel.Visible = false;
183    }
184
185    protected override void OnContentChanged() {
186      base.OnContentChanged();
187      this.categoricalMapping.Clear();
188      UpdateComboBoxes();
189      UpdateDataPoints();
190      UpdateCaption();
191      RebuildInverseIndex();
192    }
193
194    private void RebuildInverseIndex() {
195      if (Content != null) {
196        runToIndexMapping.Clear();
197        int i = 0;
198        foreach (var run in Content) {
199          runToIndexMapping.Add(run, i);
200          i++;
201        }
202      }
203    }
204
205    private void Content_ColumnNamesChanged(object sender, EventArgs e) {
206      if (InvokeRequired)
207        Invoke(new EventHandler(Content_ColumnNamesChanged), sender, e);
208      else
209        UpdateComboBoxes();
210    }
211
212    private void UpdateCaption() {
213      Caption = Content != null ? Content.OptimizerName + " Bubble Chart" : ViewAttribute.GetViewName(GetType());
214    }
215
216    private void UpdateComboBoxes() {
217      string selectedXAxis = (string)this.xAxisComboBox.SelectedItem;
218      string selectedYAxis = (string)this.yAxisComboBox.SelectedItem;
219      string selectedSizeAxis = (string)this.sizeComboBox.SelectedItem;
220      this.xAxisComboBox.Items.Clear();
221      this.yAxisComboBox.Items.Clear();
222      this.sizeComboBox.Items.Clear();
223      if (Content != null) {
224        string[] additionalAxisDimension = Enum.GetNames(typeof(AxisDimension));
225        this.xAxisComboBox.Items.AddRange(additionalAxisDimension);
226        this.xAxisComboBox.Items.AddRange(Matrix.ColumnNames.ToArray());
227        this.yAxisComboBox.Items.AddRange(additionalAxisDimension);
228        this.yAxisComboBox.Items.AddRange(Matrix.ColumnNames.ToArray());
229        string[] additionalSizeDimension = Enum.GetNames(typeof(SizeDimension));
230        this.sizeComboBox.Items.AddRange(additionalSizeDimension);
231        this.sizeComboBox.Items.AddRange(Matrix.ColumnNames.ToArray());
232        this.sizeComboBox.SelectedItem = SizeDimension.Constant.ToString();
233
234        bool changed = false;
235        if (selectedXAxis != null && xAxisComboBox.Items.Contains(selectedXAxis)) {
236          xAxisComboBox.SelectedItem = selectedXAxis;
237          changed = true;
238        }
239        if (selectedYAxis != null && yAxisComboBox.Items.Contains(selectedYAxis)) {
240          yAxisComboBox.SelectedItem = selectedYAxis;
241          changed = true;
242        }
243        if (selectedSizeAxis != null && sizeComboBox.Items.Contains(selectedSizeAxis)) {
244          sizeComboBox.SelectedItem = selectedSizeAxis;
245          changed = true;
246        }
247        if (changed) {
248          UpdateDataPoints();
249          UpdateAxisLabels();
250        }
251      }
252    }
253
254    private void Content_AlgorithmNameChanged(object sender, EventArgs e) {
255      if (InvokeRequired)
256        Invoke(new EventHandler(Content_AlgorithmNameChanged), sender, e);
257      else UpdateCaption();
258    }
259
260    private void Content_Reset(object sender, EventArgs e) {
261      if (InvokeRequired)
262        Invoke(new EventHandler(Content_Reset), sender, e);
263      else {
264        this.categoricalMapping.Clear();
265        RebuildInverseIndex();
266        UpdateDataPoints();
267        UpdateAxisLabels();
268      }
269    }
270
271    private void UpdateDataPoints() {
272      Series series = this.chart.Series[0];
273      series.Points.Clear();
274      runToDataPointMapping.Clear();
275      selectedRuns.Clear();
276
277      chart.ChartAreas[0].AxisX.IsMarginVisible = xAxisValue != AxisDimension.Index.ToString();
278      chart.ChartAreas[0].AxisY.IsMarginVisible = yAxisValue != AxisDimension.Index.ToString();
279
280      if (Content != null) {
281        foreach (IRun run in this.Content)
282          this.AddDataPoint(run);
283
284        if (this.chart.Series[0].Points.Count == 0)
285          noRunsLabel.Visible = true;
286        else {
287          noRunsLabel.Visible = false;
288          UpdateMarkerSizes();
289          UpdateCursorInterval();
290        }
291      }
292      xTrackBar.Value = 0;
293      yTrackBar.Value = 0;
294
295      //needed to set axis back to automatic and refresh them, otherwise their values may remain NaN
296      var xAxis = chart.ChartAreas[0].AxisX;
297      var yAxis = chart.ChartAreas[0].AxisY;
298      SetAutomaticUpdateOfAxis(xAxis, true);
299      SetAutomaticUpdateOfAxis(yAxis, true);
300      chart.Refresh();
301    }
302
303    private void UpdateMarkerSizes() {
304      var series = chart.Series[0];
305      var sizeValues = series.Points.Select(p => p.YValues[1]);
306
307      double minSizeValue = sizeValues.Min();
308      double maxSizeValue = sizeValues.Max();
309      double sizeRange = maxSizeValue - minSizeValue;
310
311      const int smallestBubbleSize = 5;
312
313      foreach (DataPoint point in series.Points) {
314        //calculates the relative size of the data point  0 <= relativeSize <= 1
315        double relativeSize = (point.YValues[1] - minSizeValue);
316        if (sizeRange > double.Epsilon) {
317          relativeSize /= sizeRange;
318
319          //invert bubble sizes if the value of the trackbar is negative
320          if (sizeTrackBar.Value < 0) relativeSize = Math.Abs(relativeSize - 1);
321        } else relativeSize = 1;
322
323        double sizeChange = Math.Abs(sizeTrackBar.Value) * relativeSize;
324        point.MarkerSize = (int)Math.Round(sizeChange + smallestBubbleSize);
325      }
326    }
327
328    private void UpdateDataPointJitter() {
329      var xAxis = this.chart.ChartAreas[0].AxisX;
330      var yAxis = this.chart.ChartAreas[0].AxisY;
331
332      SetAutomaticUpdateOfAxis(xAxis, false);
333      SetAutomaticUpdateOfAxis(yAxis, false);
334
335      double xAxisRange = xAxis.Maximum - xAxis.Minimum;
336      double yAxisRange = yAxis.Maximum - yAxis.Minimum;
337
338      foreach (DataPoint point in chart.Series[0].Points) {
339        IRun run = (IRun)point.Tag;
340        double xValue = GetValue(run, xAxisValue).Value;
341        double yValue = GetValue(run, yAxisValue).Value;
342
343        if (!xJitterFactor.IsAlmost(0.0))
344          xValue += 0.1 * GetXJitter(run) * xJitterFactor * (xAxisRange);
345        if (!yJitterFactor.IsAlmost(0.0))
346          yValue += 0.1 * GetYJitter(run) * yJitterFactor * (yAxisRange);
347
348        point.XValue = xValue;
349        point.YValues[0] = yValue;
350      }
351
352    }
353
354    // sets an axis to automatic or restrains it to its current values
355    // this is used that none of the set values is changed when jitter is applied, so that the chart stays the same
356    private void SetAutomaticUpdateOfAxis(Axis axis, bool enabled) {
357      if (enabled) {
358        axis.Maximum = double.NaN;
359        axis.Minimum = double.NaN;
360        axis.MajorGrid.Interval = double.NaN;
361        axis.MajorTickMark.Interval = double.NaN;
362        axis.LabelStyle.Interval = double.NaN;
363      } else {
364        axis.Minimum = axis.Minimum;
365        axis.Maximum = axis.Maximum;
366        axis.MajorGrid.Interval = axis.MajorGrid.Interval;
367        axis.MajorTickMark.Interval = axis.MajorTickMark.Interval;
368        axis.LabelStyle.Interval = axis.LabelStyle.Interval;
369      }
370    }
371
372    private void AddDataPoint(IRun run) {
373      double? xValue;
374      double? yValue;
375      double? sizeValue;
376      Series series = this.chart.Series[0];
377
378      xValue = GetValue(run, xAxisValue);
379      yValue = GetValue(run, yAxisValue);
380      sizeValue = GetValue(run, sizeAxisValue);
381
382      if (xValue.HasValue && yValue.HasValue && sizeValue.HasValue) {
383        xValue = xValue.Value;
384        yValue = yValue.Value;
385
386        if (run.Visible) {
387          DataPoint point = new DataPoint(xValue.Value, new double[] { yValue.Value, sizeValue.Value });
388          point.Tag = run;
389          series.Points.Add(point);
390          if (!runToDataPointMapping.ContainsKey(run)) runToDataPointMapping.Add(run, new List<DataPoint>());
391          runToDataPointMapping[run].Add(point);
392          UpdateRun(run);
393        }
394      }
395    }
396    private double? GetValue(IRun run, string columnName) {
397      if (run == null || string.IsNullOrEmpty(columnName))
398        return null;
399
400      if (Enum.IsDefined(typeof(AxisDimension), columnName)) {
401        AxisDimension axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), columnName);
402        return GetValue(run, axisDimension);
403      } else if (Enum.IsDefined(typeof(SizeDimension), columnName)) {
404        SizeDimension sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), columnName);
405        return GetValue(run, sizeDimension);
406      } else {
407        int columnIndex = Matrix.ColumnNames.ToList().IndexOf(columnName);
408        IItem value = Content.GetValue(run, columnIndex);
409        if (value == null)
410          return null;
411
412        DoubleValue doubleValue = value as DoubleValue;
413        IntValue intValue = value as IntValue;
414        TimeSpanValue timeSpanValue = value as TimeSpanValue;
415        double? ret = null;
416        if (doubleValue != null) {
417          if (!double.IsNaN(doubleValue.Value) && !double.IsInfinity(doubleValue.Value))
418            ret = doubleValue.Value;
419        } else if (intValue != null)
420          ret = intValue.Value;
421        else if (timeSpanValue != null) {
422          ret = timeSpanValue.Value.TotalSeconds;
423        } else
424          ret = GetCategoricalValue(columnIndex, value.ToString());
425
426        return ret;
427      }
428    }
429    private double GetCategoricalValue(int dimension, string value) {
430      if (!this.categoricalMapping.ContainsKey(dimension)) {
431        this.categoricalMapping[dimension] = new Dictionary<object, double>();
432        var orderedCategories = Content.Where(r => r.Visible).Select(r => Content.GetValue(r, dimension).ToString())
433                                    .Distinct()
434                                    .OrderBy(x => x, new NaturalStringComparer());
435        int count = 1;
436        foreach (var category in orderedCategories) {
437          this.categoricalMapping[dimension].Add(category, count);
438          count++;
439        }
440      }
441      return this.categoricalMapping[dimension][value];
442    }
443
444    private double GetValue(IRun run, AxisDimension axisDimension) {
445      double value = double.NaN;
446      switch (axisDimension) {
447        case AxisDimension.Index: {
448            value = runToIndexMapping[run];
449            break;
450          }
451        default: {
452            throw new ArgumentException("No handling strategy for " + axisDimension.ToString() + " is defined.");
453          }
454      }
455      return value;
456    }
457    private double GetValue(IRun run, SizeDimension sizeDimension) {
458      double value = double.NaN;
459      switch (sizeDimension) {
460        case SizeDimension.Constant: {
461            value = 2;
462            break;
463          }
464        default: {
465            throw new ArgumentException("No handling strategy for " + sizeDimension.ToString() + " is defined.");
466          }
467      }
468      return value;
469    }
470    private void UpdateCursorInterval() {
471      double xMin = double.MaxValue;
472      double xMax = double.MinValue;
473      double yMin = double.MaxValue;
474      double yMax = double.MinValue;
475
476      foreach (var point in chart.Series[0].Points) {
477        if (point.IsEmpty) continue;
478        if (point.XValue < xMin) xMin = point.XValue;
479        if (point.XValue > xMax) xMax = point.XValue;
480        if (point.YValues[0] < yMin) yMin = point.YValues[0];
481        if (point.YValues[0] > yMax) yMax = point.YValues[0];
482      }
483
484      double xRange = 0.0;
485      double yRange = 0.0;
486      if (xMin != double.MaxValue && xMax != double.MinValue) xRange = xMax - xMin;
487      if (yMin != double.MaxValue && yMax != double.MinValue) yRange = yMax - yMin;
488
489      if (xRange.IsAlmost(0.0)) xRange = 1.0;
490      if (yRange.IsAlmost(0.0)) yRange = 1.0;
491      double xDigits = (int)Math.Log10(xRange) - 3;
492      double yDigits = (int)Math.Log10(yRange) - 3;
493      double xZoomInterval = Math.Pow(10, xDigits);
494      double yZoomInterval = Math.Pow(10, yDigits);
495      this.chart.ChartAreas[0].CursorX.Interval = xZoomInterval;
496      this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval;
497
498      //code to handle TimeSpanValues correct
499      int axisDimensionCount = Enum.GetNames(typeof(AxisDimension)).Count();
500      int columnIndex = xAxisComboBox.SelectedIndex - axisDimensionCount;
501      if (columnIndex >= 0 && Content.GetValue(0, columnIndex) is TimeSpanValue)
502        this.chart.ChartAreas[0].CursorX.Interval = 1;
503      columnIndex = yAxisComboBox.SelectedIndex - axisDimensionCount;
504      if (columnIndex >= 0 && Content.GetValue(0, columnIndex) is TimeSpanValue)
505        this.chart.ChartAreas[0].CursorY.Interval = 1;
506    }
507
508    #region Drag & drop and tooltip
509    private void chart_MouseDoubleClick(object sender, MouseEventArgs e) {
510      HitTestResult h = this.chart.HitTest(e.X, e.Y, ChartElementType.DataPoint);
511      if (h.ChartElementType == ChartElementType.DataPoint) {
512        IRun run = (IRun)((DataPoint)h.Object).Tag;
513        IContentView view = MainFormManager.MainForm.ShowContent(run);
514        if (view != null) {
515          view.ReadOnly = this.ReadOnly;
516          view.Locked = this.Locked;
517        }
518
519        this.chart.ChartAreas[0].CursorX.SelectionStart = this.chart.ChartAreas[0].CursorX.SelectionEnd;
520        this.chart.ChartAreas[0].CursorY.SelectionStart = this.chart.ChartAreas[0].CursorY.SelectionEnd;
521      }
522      UpdateAxisLabels();
523    }
524
525    private void chart_MouseUp(object sender, MouseEventArgs e) {
526      if (!isSelecting) return;
527
528      System.Windows.Forms.DataVisualization.Charting.Cursor xCursor = chart.ChartAreas[0].CursorX;
529      System.Windows.Forms.DataVisualization.Charting.Cursor yCursor = chart.ChartAreas[0].CursorY;
530
531      double minX = Math.Min(xCursor.SelectionStart, xCursor.SelectionEnd);
532      double maxX = Math.Max(xCursor.SelectionStart, xCursor.SelectionEnd);
533      double minY = Math.Min(yCursor.SelectionStart, yCursor.SelectionEnd);
534      double maxY = Math.Max(yCursor.SelectionStart, yCursor.SelectionEnd);
535
536      //check for click to select a single model
537      if (minX == maxX && minY == maxY) {
538        HitTestResult hitTest = chart.HitTest(e.X, e.Y);
539        if (hitTest.ChartElementType == ChartElementType.DataPoint) {
540          int pointIndex = hitTest.PointIndex;
541          var point = chart.Series[0].Points[pointIndex];
542          IRun run = (IRun)point.Tag;
543          point.Color = Color.Red;
544          point.MarkerStyle = MarkerStyle.Cross;
545          selectedRuns.Add(run);
546
547        } else ClearSelectedRuns();
548      } else {
549        foreach (DataPoint point in this.chart.Series[0].Points) {
550          if (point.XValue < minX || point.XValue >= maxX) continue;
551          if (point.YValues[0] < minY || point.YValues[0] >= maxY) continue;
552          point.MarkerStyle = MarkerStyle.Cross;
553          point.Color = Color.Red;
554          IRun run = (IRun)point.Tag;
555          selectedRuns.Add(run);
556        }
557      }
558      this.chart.ChartAreas[0].CursorX.SelectionStart = this.chart.ChartAreas[0].CursorX.SelectionEnd;
559      this.chart.ChartAreas[0].CursorY.SelectionStart = this.chart.ChartAreas[0].CursorY.SelectionEnd;
560    }
561
562    private void chart_MouseMove(object sender, MouseEventArgs e) {
563      if (Control.MouseButtons != MouseButtons.None) return;
564      HitTestResult h = this.chart.HitTest(e.X, e.Y);
565      string newTooltipText = string.Empty;
566      string oldTooltipText;
567      if (h.ChartElementType == ChartElementType.DataPoint) {
568        IRun run = (IRun)((DataPoint)h.Object).Tag;
569        newTooltipText = BuildTooltip(run);
570      } else if (h.ChartElementType == ChartElementType.AxisLabels) {
571        newTooltipText = ((CustomLabel)h.Object).ToolTip;
572      }
573
574      oldTooltipText = this.tooltip.GetToolTip(chart);
575      if (newTooltipText != oldTooltipText)
576        this.tooltip.SetToolTip(chart, newTooltipText);
577    }
578
579    private string BuildTooltip(IRun run) {
580      string tooltip;
581      tooltip = run.Name + System.Environment.NewLine;
582
583      double? xValue = this.GetValue(run, (string)xAxisComboBox.SelectedItem);
584      double? yValue = this.GetValue(run, (string)yAxisComboBox.SelectedItem);
585      double? sizeValue = this.GetValue(run, (string)sizeComboBox.SelectedItem);
586
587      string xString = xValue == null ? string.Empty : xValue.Value.ToString();
588      string yString = yValue == null ? string.Empty : yValue.Value.ToString();
589      string sizeString = sizeValue == null ? string.Empty : sizeValue.Value.ToString();
590
591      //code to handle TimeSpanValues correct
592      int axisDimensionCount = Enum.GetNames(typeof(AxisDimension)).Count();
593      int columnIndex = xAxisComboBox.SelectedIndex - axisDimensionCount;
594      if (xValue.HasValue && columnIndex > 0 && Content.GetValue(0, columnIndex) is TimeSpanValue) {
595        TimeSpan time = TimeSpan.FromSeconds(xValue.Value);
596        xString = string.Format("{0:00}:{1:00}:{2:00.00}", (int)time.TotalHours, time.Minutes, time.Seconds);
597      }
598      columnIndex = yAxisComboBox.SelectedIndex - axisDimensionCount;
599      if (yValue.HasValue && columnIndex > 0 && Content.GetValue(0, columnIndex) is TimeSpanValue) {
600        TimeSpan time = TimeSpan.FromSeconds(yValue.Value);
601        yString = string.Format("{0:00}:{1:00}:{2:00.00}", (int)time.TotalHours, time.Minutes, time.Seconds);
602      }
603
604      tooltip += xAxisComboBox.SelectedItem + " : " + xString + Environment.NewLine;
605      tooltip += yAxisComboBox.SelectedItem + " : " + yString + Environment.NewLine;
606      tooltip += sizeComboBox.SelectedItem + " : " + sizeString + Environment.NewLine;
607
608      return tooltip;
609    }
610    #endregion
611
612    #region GUI events and updating
613    private double GetXJitter(IRun run) {
614      if (!this.xJitter.ContainsKey(run))
615        this.xJitter[run] = random.NextDouble() * 2.0 - 1.0;
616      return this.xJitter[run];
617    }
618    private double GetYJitter(IRun run) {
619      if (!this.yJitter.ContainsKey(run))
620        this.yJitter[run] = random.NextDouble() * 2.0 - 1.0;
621      return this.yJitter[run];
622    }
623    private void jitterTrackBar_ValueChanged(object sender, EventArgs e) {
624      this.xJitterFactor = xTrackBar.Value / 100.0;
625      this.yJitterFactor = yTrackBar.Value / 100.0;
626      UpdateDataPointJitter();
627    }
628    private void sizeTrackBar_ValueChanged(object sender, EventArgs e) {
629      UpdateMarkerSizes();
630    }
631
632    private void AxisComboBox_SelectedValueChanged(object sender, EventArgs e) {
633      bool axisSelected = xAxisComboBox.SelectedIndex != -1 && yAxisComboBox.SelectedIndex != -1;
634      xTrackBar.Enabled = yTrackBar.Enabled = axisSelected;
635      colorXAxisButton.Enabled = colorYAxisButton.Enabled = axisSelected;
636
637      xAxisValue = (string)xAxisComboBox.SelectedItem;
638      yAxisValue = (string)yAxisComboBox.SelectedItem;
639      sizeAxisValue = (string)sizeComboBox.SelectedItem;
640
641      UpdateDataPoints();
642      UpdateAxisLabels();
643    }
644    private void UpdateAxisLabels() {
645      Axis xAxis = this.chart.ChartAreas[0].AxisX;
646      Axis yAxis = this.chart.ChartAreas[0].AxisY;
647      int axisDimensionCount = Enum.GetNames(typeof(AxisDimension)).Count();
648      SetCustomAxisLabels(xAxis, xAxisComboBox.SelectedIndex - axisDimensionCount);
649      SetCustomAxisLabels(yAxis, yAxisComboBox.SelectedIndex - axisDimensionCount);
650      if (xAxisComboBox.SelectedItem != null)
651        xAxis.Title = xAxisComboBox.SelectedItem.ToString();
652      if (yAxisComboBox.SelectedItem != null)
653        yAxis.Title = yAxisComboBox.SelectedItem.ToString();
654    }
655
656    private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) {
657      this.UpdateAxisLabels();
658    }
659
660    private void SetCustomAxisLabels(Axis axis, int dimension) {
661      axis.CustomLabels.Clear();
662      if (categoricalMapping.ContainsKey(dimension)) {
663        foreach (var pair in categoricalMapping[dimension]) {
664          string labelText = pair.Key.ToString();
665          CustomLabel label = new CustomLabel();
666          label.ToolTip = labelText;
667          if (labelText.Length > 25)
668            labelText = labelText.Substring(0, 25) + " ... ";
669          label.Text = labelText;
670          label.GridTicks = GridTickTypes.TickMark;
671          label.FromPosition = pair.Value - 0.5;
672          label.ToPosition = pair.Value + 0.5;
673          axis.CustomLabels.Add(label);
674        }
675      } else if (dimension > 0 && Content.GetValue(0, dimension) is TimeSpanValue) {
676        this.chart.ChartAreas[0].RecalculateAxesScale();
677        for (double i = axis.Minimum; i <= axis.Maximum; i += axis.LabelStyle.Interval) {
678          TimeSpan time = TimeSpan.FromSeconds(i);
679          string x = string.Format("{0:00}:{1:00}:{2:00}", time.Hours, time.Minutes, time.Seconds);
680          axis.CustomLabels.Add(i - axis.LabelStyle.Interval / 2, i + axis.LabelStyle.Interval / 2, x);
681        }
682      }
683    }
684
685    private void zoomButton_CheckedChanged(object sender, EventArgs e) {
686      this.isSelecting = selectButton.Checked;
687      this.colorDialogButton.Enabled = this.isSelecting;
688      this.colorRunsButton.Enabled = this.isSelecting;
689      this.hideRunsButton.Enabled = this.isSelecting;
690      this.chart.ChartAreas[0].AxisX.ScaleView.Zoomable = !isSelecting;
691      this.chart.ChartAreas[0].AxisY.ScaleView.Zoomable = !isSelecting;
692      ClearSelectedRuns();
693    }
694
695    private IRun runToHide = null;
696    private void ContextMenuStrip_Opening(object sender, System.ComponentModel.CancelEventArgs e) {
697      var pos = Control.MousePosition;
698      var chartPos = chart.PointToClient(pos);
699
700      HitTestResult h = this.chart.HitTest(chartPos.X, chartPos.Y);
701      if (h.ChartElementType == ChartElementType.DataPoint) {
702        runToHide = (IRun)((DataPoint)h.Object).Tag;
703        hideRunToolStripMenuItem.Visible = true;
704      } else {
705        runToHide = null;
706        hideRunToolStripMenuItem.Visible = false;
707      }
708
709    }
710    private void hideRunToolStripMenuItem_Click(object sender, EventArgs e) {
711      var constraint = Content.Constraints.OfType<RunCollectionContentConstraint>().FirstOrDefault(c => c.Active);
712      if (constraint == null) {
713        constraint = new RunCollectionContentConstraint();
714        Content.Constraints.Add(constraint);
715        constraint.Active = true;
716      }
717      constraint.ConstraintData.Add(runToHide);
718    }
719    private void hideRunsButton_Click(object sender, EventArgs e) {
720      if (!selectedRuns.Any()) return;
721      var constraint = new RunCollectionContentConstraint();
722      constraint.ConstraintData = new ItemSet<IRun>(selectedRuns);
723      Content.Constraints.Add(constraint);
724      ClearSelectedRuns();
725      constraint.Active = true;
726    }
727
728    private void ClearSelectedRuns() {
729      foreach (var run in selectedRuns) {
730        foreach (var point in runToDataPointMapping[run]) {
731          point.MarkerStyle = MarkerStyle.Circle;
732          point.Color = Color.FromArgb(255 - transparencyTrackBar.Value, run.Color);
733        }
734      }
735      selectedRuns.Clear();
736    }
737
738    private void openBoxPlotViewToolStripMenuItem_Click(object sender, EventArgs e) {
739      RunCollectionBoxPlotView boxplotView = new RunCollectionBoxPlotView();
740      boxplotView.Content = this.Content;
741      boxplotView.xAxisComboBox.SelectedItem = xAxisComboBox.SelectedItem;
742      boxplotView.yAxisComboBox.SelectedItem = yAxisComboBox.SelectedItem;
743      boxplotView.Show();
744    }
745
746    private void getDataAsMatrixToolStripMenuItem_Click(object sender, EventArgs e) {
747      int xCol = Matrix.ColumnNames.ToList().IndexOf(xAxisValue);
748      int yCol = Matrix.ColumnNames.ToList().IndexOf(yAxisValue);
749
750      var grouped = new Dictionary<string, List<string>>();
751      Dictionary<double, string> reverseMapping = null;
752      if (categoricalMapping.ContainsKey(xCol))
753        reverseMapping = categoricalMapping[xCol].ToDictionary(x => x.Value, y => y.Key.ToString());
754      foreach (var run in Content.Where(r => r.Visible)) {
755        var x = GetValue(run, xAxisValue);
756        object y;
757        if (categoricalMapping.ContainsKey(yCol))
758          y = Content.GetValue(run, yAxisValue);
759        else y = GetValue(run, yAxisValue);
760        if (!(x.HasValue && y != null)) continue;
761
762        var category = reverseMapping == null ? x.Value.ToString() : reverseMapping[x.Value];
763        if (!grouped.ContainsKey(category)) grouped[category] = new List<string>();
764        grouped[category].Add(y.ToString());
765      }
766
767      if (!grouped.Any()) return;
768      var matrix = new StringMatrix(grouped.Values.Max(x => x.Count), grouped.Count) {
769        ColumnNames = grouped.Keys.ToArray()
770      };
771      int i = 0;
772      foreach (var col in matrix.ColumnNames) {
773        int j = 0;
774        foreach (var y in grouped[col])
775          matrix[j++, i] = y;
776        i++;
777      }
778      matrix.SortableView = false;
779      var view = MainFormManager.MainForm.ShowContent(matrix);
780      view.ReadOnly = true;
781    }
782
783    private void transparencyTrackBar_ValueChanged(object sender, EventArgs e) {
784      foreach (var run in Content)
785        UpdateRun(run);
786    }
787    #endregion
788
789    #region coloring
790    private void colorDialogButton_Click(object sender, EventArgs e) {
791      if (colorDialog.ShowDialog(this) == DialogResult.OK) {
792        this.colorDialogButton.Image = this.GenerateImage(16, 16, this.colorDialog.Color);
793      }
794    }
795    private Image GenerateImage(int width, int height, Color fillColor) {
796      Image colorImage = new Bitmap(width, height);
797      using (Graphics gfx = Graphics.FromImage(colorImage)) {
798        using (SolidBrush brush = new SolidBrush(fillColor)) {
799          gfx.FillRectangle(brush, 0, 0, width, height);
800        }
801      }
802      return colorImage;
803    }
804
805    private void colorRunsButton_Click(object sender, EventArgs e) {
806      if (!selectedRuns.Any()) return;
807      Content.UpdateOfRunsInProgress = true;
808      foreach (var run in selectedRuns)
809        run.Color = colorDialog.Color;
810
811      ClearSelectedRuns();
812      Content.UpdateOfRunsInProgress = false;
813    }
814
815    private void colorXAxisButton_Click(object sender, EventArgs e) {
816      ColorRuns(xAxisValue);
817    }
818    private void colorYAxisButton_Click(object sender, EventArgs e) {
819      ColorRuns(yAxisValue);
820    }
821    private void ColorRuns(string axisValue) {
822      var runs = Content.Where(r => r.Visible).Select(r => new { Run = r, Value = GetValue(r, axisValue) }).Where(r => r.Value.HasValue).ToList();
823      double minValue = runs.Min(r => r.Value.Value);
824      double maxValue = runs.Max(r => r.Value.Value);
825      double range = maxValue - minValue;
826      // UpdateOfRunsInProgress has to be set to true, otherwise run_Changed is called all the time (also in other views)
827      Content.UpdateOfRunsInProgress = true;
828      if (range.IsAlmost(0)) {
829        Color c = ColorGradient.Colors[0];
830        runs.ForEach(r => r.Run.Color = c);
831      } else {
832        int maxColorIndex = ColorGradient.Colors.Count - 1;
833        foreach (var r in runs) {
834          int colorIndex = (int)(maxColorIndex * (r.Value - minValue) / (range));
835          r.Run.Color = ColorGradient.Colors[colorIndex];
836        }
837      }
838      Content.UpdateOfRunsInProgress = false;
839    }
840    #endregion
841  }
842}
Note: See TracBrowser for help on using the repository browser.