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

Last change on this file since 9435 was 9435, checked in by mkommend, 6 years ago

#2016: Added patch from sforsten, that improves the BubbleChart- and BoxPlotView.

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