Free cookie consent management tool by TermsFeed Policy Generator

source: branches/BubbleChart/HeuristicLab.Optimization.BubbleChart/3.3/BubbleChartView.cs @ 15016

Last change on this file since 15016 was 12884, checked in by pfleck, 9 years ago

#2379
Added tooltip when hovering a data point.
Click on a data point opens the corresponding DataItem or both if x and y values came from different DataItems.

File size: 28.6 KB
RevLine 
[12388]1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2015 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.Linq;
25using System.Windows.Forms;
26using System.Windows.Forms.DataVisualization.Charting;
27using HeuristicLab.Common;
28using HeuristicLab.Core;
29using HeuristicLab.Core.Views;
30using HeuristicLab.Data;
31using HeuristicLab.MainForm;
32
33namespace HeuristicLab.Optimization.BubbleChart {
34  [View("Bubble Chart (Recursive)")]
[12880]35  [Content(typeof(RecursiveDataItem), true)]
[12388]36  public partial class BubbleChartView : ItemView {
[12554]37    private const string Separator = ":";
38
[12388]39    private enum SizeDimension { Constant = 0 }
40    private enum AxisDimension { Index = 0 }
41
42    private readonly Dictionary<RecursiveDataItem, int> itemToIndexMapping = new Dictionary<RecursiveDataItem, int>();
43    private readonly Dictionary<string, Dictionary<object, double>> categoricalMapping = new Dictionary<string, Dictionary<object, double>>();
44
[12881]45    private readonly Dictionary<RecursiveDataItem, double> xJitter = new Dictionary<RecursiveDataItem, double>();
46    private readonly Dictionary<RecursiveDataItem, double> yJitter = new Dictionary<RecursiveDataItem, double>();
47    private readonly Random random = new Random();
48
49
[12498]50    private string XAxisValue { get { return (string)xAxisComboBox.SelectedItem; } }
51    private string YAxisValue { get { return (string)yAxisComboBox.SelectedItem; } }
52    private string SizeAxisValue { get { return (string)sizeComboBox.SelectedItem; } }
53
[12499]54    private bool updating = false;
55
[12881]56    private double xJitterFactor = 0.0;
57    private double yJitterFactor = 0.0;
58
[12388]59    public new RecursiveDataItem Content {
60      get { return (RecursiveDataItem)base.Content; }
61      set { base.Content = value; }
62    }
63
64    public BubbleChartView() {
65      InitializeComponent();
[12598]66
67      chart.CustomizeAllChartAreas();
68      chart.ChartAreas[0].CursorX.Interval = 1;
69      chart.ChartAreas[0].CursorY.Interval = 1;
70      chart.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
71      chart.ChartAreas[0].AxisY.ScaleView.Zoomable = true;
[12388]72    }
73
74    protected override void OnContentChanged() {
75      base.OnContentChanged();
76
77      UpdateTreeView();
[12459]78      UpdateLevelControl();
79
[12388]80      UpdateComboBoxes();
81      UpdateDataPoints();
82      //UpdateCaption();
83    }
[12459]84    protected override void SetEnabledStateOfControls() {
85      base.SetEnabledStateOfControls();
86      levelNumericUpDown.Enabled = Content != null;
[12881]87      xJitterTrackBar.Enabled = Content != null;
88      yJitterTrackBar.Enabled = Content != null;
89      xAxisComboBox.Enabled = Content != null;
90      yAxisComboBox.Enabled = Content != null;
[12459]91    }
[12388]92
93    #region Tree Queries
94    private IEnumerable<RecursiveDataItem> GetAvailableItems() {
[12459]95      return IterateCheckedNodes()
96        .Select(n => (RecursiveDataItem)n.Tag);
[12388]97    }
[12459]98    private IEnumerable<string> GetAvailableKeys() {
[12880]99      var collector = new List<string>();
[12554]100      GetAvailableKeys(Content, collector);
[12880]101      return collector.Distinct();
[12388]102    }
[12880]103    private void GetAvailableKeys(RecursiveDataItem node, List<string> collector) {
104      collector.AddRange(node.Data.Keys);
[12554]105
106      foreach (var child in node.Children)
107        GetAvailableKeys(child, collector);
108    }
[12388]109    #endregion
110
111    #region Update Controls
[12459]112    private void UpdateLevelControl() {
113      if (Content == null) return;
114      if (treeView.Nodes.Count > 0)
115        levelNumericUpDown.Maximum = IterateAllNodes().Max(t => t.Level);
116      else
117        levelNumericUpDown.Maximum = 0;
118    }
119
[12388]120    private void UpdateTreeView() {
121      treeView.Nodes.Clear();
[12459]122      if (Content != null)
[12389]123        treeView.Nodes.Add(CreateTreeNode(Content));
[12388]124      treeView.ExpandAll();
125      splitContainer.Panel1Collapsed = treeView.Nodes.Count == 0;
[12459]126      if (treeView.Nodes.Count > 0)
127        treeView.SelectedNode = treeView.Nodes[0];
[12388]128    }
129    private TreeNode CreateTreeNode(RecursiveDataItem item) {
130      var node = new TreeNode(item.Name) {
131        Tag = item,
132        Checked = true
133      };
134      foreach (var child in item.Children)
135        node.Nodes.Add(CreateTreeNode(child));
136      return node;
137    }
138
[12459]139    private void UpdateTreeViewCheckBoxes() {
140      int level = (int)levelNumericUpDown.Value;
141      bool includeChildren = includeChildrenCheckBox.Checked;
[12498]142      foreach (var node in IterateAllNodes()) {
143        bool @checked = includeChildren ? node.Level >= level : node.Level == level;
144        if (node.Checked != @checked)
145          node.Checked = @checked;
146      }
[12459]147    }
148
[12388]149    private void UpdateComboBoxes() {
150      var selectedXAxis = (string)xAxisComboBox.SelectedItem;
151      var selectedYAxis = (string)yAxisComboBox.SelectedItem;
152      var selectedSizeAxis = (string)sizeComboBox.SelectedItem;
153      xAxisComboBox.Items.Clear();
154      yAxisComboBox.Items.Clear();
155      sizeComboBox.Items.Clear();
156
157      if (Content != null) {
158        var axisNames = GetAvailableKeys().ToArray();
159        var additionalAxisDimension = Enum.GetNames(typeof(AxisDimension));
160        var additionalSizeDimension = Enum.GetNames(typeof(SizeDimension));
161
[12880]162        //xAxisComboBox.Items.AddRange(additionalAxisDimension);
[12388]163        xAxisComboBox.Items.AddRange(axisNames);
[12880]164        //yAxisComboBox.Items.AddRange(additionalAxisDimension);
[12388]165        yAxisComboBox.Items.AddRange(axisNames);
166        sizeComboBox.Items.AddRange(additionalSizeDimension);
167        sizeComboBox.Items.AddRange(axisNames);
168
169        bool changed = false;
170        if (selectedXAxis != null && xAxisComboBox.Items.Contains(selectedXAxis)) {
171          xAxisComboBox.SelectedItem = selectedXAxis;
172          changed = true;
173        }
174        if (selectedYAxis != null && yAxisComboBox.Items.Contains(selectedYAxis)) {
175          yAxisComboBox.SelectedItem = selectedYAxis;
176          changed = true;
177        }
178        if (selectedSizeAxis != null && sizeComboBox.Items.Contains(selectedSizeAxis)) {
179          sizeComboBox.SelectedItem = selectedSizeAxis;
180          changed = true;
[12498]181        } else sizeComboBox.SelectedItem = SizeDimension.Constant.ToString();
[12388]182        if (changed) {
183          UpdateDataPoints();
[12498]184          UpdateAxisLabels();
[12388]185        }
186      }
187    }
188
189    private void UpdateDataPoints() {
190      var series = chart.Series[0];
191      series.Points.Clear();
192      itemToIndexMapping.Clear();
193      categoricalMapping.Clear();
194      RebuildInverseIndex();
195
[12647]196      chart.ChartAreas[0].AxisX.IsMarginVisible = !(XAxisValue != null && XAxisValue.Contains(AxisDimension.Index.ToString()));
197      chart.ChartAreas[0].AxisY.IsMarginVisible = !(YAxisValue != null && YAxisValue.Contains(AxisDimension.Index.ToString()));
[12388]198
[12882]199      if (Content != null && !string.IsNullOrEmpty(XAxisValue) && !string.IsNullOrEmpty(YAxisValue)) {
200        var xss = GetValues(XAxisValue);
201        var yss = GetValues(YAxisValue);
[12647]202
[12882]203        if (xss.Count == yss.Count) {
204          for (int i = 0; i < xss.Count; i++)
205            AddPoints(xss[i], yss[i], series);
206        } else if (xss.Count == 1 || yss.Count == 1) {
207          for (int i = 0; i < xss.Count; i++)
208            for (int j = 0; j < yss.Count; j++)
209              AddPoints(xss[i], yss[j], series);
210        }
211      }
[12647]212
[12882]213      if (chart.Series[0].Points.Count == 0) {
214        noDataLabel.Visible = true;
215      } else {
216        noDataLabel.Visible = false;
217        UpdateMarkerSizes();
218        UpdateCursorInterval();
[12880]219      }
[12881]220
221      xJitterTrackBar.Value = 0;
222      yJitterTrackBar.Value = 0;
223
224      //needed to set axis back to automatic and refresh them, otherwise their values may remain NaN
225      var xAxis = chart.ChartAreas[0].AxisX;
226      var yAxis = chart.ChartAreas[0].AxisY;
227      SetAutomaticUpdateOfAxis(xAxis, true);
228      SetAutomaticUpdateOfAxis(yAxis, true);
229      chart.Refresh();
[12880]230    }
[12881]231    void AddPoints(Tuple<double[], RecursiveDataItem> xs, Tuple<double[], RecursiveDataItem> ys, Series series) {
232      if (xs.Item1.Length != ys.Item1.Length) return;
233      for (int k = 0; k < xs.Item1.Length; k++) {
234        series.Points.Add(new DataPoint(xs.Item1[k], new[] { ys.Item1[k], 1.0 }) {
235          Tag = Tuple.Create(xs.Item1[k], ys.Item1[k], xs.Item2, ys.Item2)
236        });
[12880]237      }
238    }
[12647]239
[12881]240    private List<Tuple<double[], RecursiveDataItem>> GetValues(string key) {
241      var collector = new List<Tuple<double[], RecursiveDataItem>>();
[12880]242      GetValues(Content, key, collector);
243      return collector;
244    }
[12881]245    private void GetValues(RecursiveDataItem node, string key, List<Tuple<double[], RecursiveDataItem>> collector) {
[12880]246      IItem item;
247      if (node.Data.TryGetValue(key, out item)) {
248        var value = ConvertToDoubles(node, key);
[12881]249        collector.Add(Tuple.Create(value, node));
[12388]250      }
[12880]251
252      foreach (var child in node.Children)
253        GetValues(child, key, collector);
[12388]254    }
255
[12880]256    private double[] ConvertToDoubles(RecursiveDataItem item, string key) {
257      IItem value = item.Data[key];
258      var doubleValue = value as DoubleValue;
259      var doubleArray = value as DoubleArray;
260      var intValue = value as IntValue;
261      var intArray = value as IntArray;
262      var timeSpanValue = value as TimeSpanValue;
263      if (doubleValue != null) return new double[1] { doubleValue.Value };
264      if (intValue != null) return new double[1] { intValue.Value };
265      if (timeSpanValue != null) return new double[1] { timeSpanValue.Value.TotalSeconds };
266      if (doubleArray != null) return doubleArray.ToArray();
267      if (intArray != null) return intArray.Select(v => (double)v).ToArray();
268      return new double[1] { GetCategoricalValue(key, value.ToString()).Value };
269    }
[12647]270
271    private IEnumerable<IEnumerable<double>> GetValues(IEnumerable<RecursiveDataItem> items, string key) {
272      if (key == null) yield break;
273      var keyTokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
274      if (keyTokens.Length == 1) {
275        if (Enum.IsDefined(typeof(SizeDimension), key)) {
276          var sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), key);
277          yield return GetValue(null, sizeDimension).ToEnumerable();
278        } else
279          foreach (var item in items) {
280            if (Enum.IsDefined(typeof(AxisDimension), key)) {
281              var axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), key);
282              yield return GetValue(item, axisDimension).ToEnumerable();
283            } else if (item.Data.ContainsKey(key)) {
284              yield return item.Data[key] as IEnumerable<double> ?? ConvertToDouble(item, key).ToEnumerable();
285            }
286          }
287      } else if (keyTokens.Length == 2) {
288        string parentName = keyTokens[0];
289        key = keyTokens[1];
290        foreach (var item in items.Where(item => item.Name == parentName)) {
291          foreach (var child in item.Children.Where(child => child.Data.ContainsKey(key))) {
292            yield return child.Data[key] as IEnumerable<double> ?? ConvertToDouble(child, key).ToEnumerable();
293          }
294        }
295      } else throw new InvalidOperationException("key either contains a single key or a child/data key");
296
297    }
298
[12554]299    private IEnumerable<double> GetValues(RecursiveDataItem parent, string key) {
300      if (parent == null || string.IsNullOrEmpty(key))
301        return Enumerable.Empty<double>();
302      var tokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
303      if (tokens.Length == 2) {
304        var parentKey = tokens[0];
305        var dataKey = tokens[1];
306        if (parent.Name == parentKey) {
307          return parent.Children
308            .Where(child => child.Data.ContainsKey(dataKey))
[12595]309            .Select(child => ConvertToDouble(child, dataKey));
[12554]310        }
311        return Enumerable.Empty<double>();
[12388]312      } else {
[12554]313        var item = parent;
314        if (item == null || string.IsNullOrEmpty(key))
315          return Enumerable.Empty<double>();
316
317        if (Enum.IsDefined(typeof(AxisDimension), key)) {
318          var axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), key);
319          return GetValue(item, axisDimension).ToEnumerable();
320        } else if (Enum.IsDefined(typeof(SizeDimension), key)) {
321          var sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), key);
322          return GetValue(item, sizeDimension).ToEnumerable();
323        } else if (item.Data.ContainsKey(key)) {
[12595]324
325          return ConvertToDouble(item, key).ToEnumerable();
[12554]326        } else {
327          return Enumerable.Empty<double>();
328        }
[12388]329      }
330    }
[12554]331
[12595]332    private double ConvertToDouble(RecursiveDataItem item, string key) {
333      IItem value = item.Data[key];
[12554]334      var doubleValue = value as DoubleValue;
335      var intValue = value as IntValue;
336      var timeSpanValue = value as TimeSpanValue;
337      double? ret = null;
338      if (doubleValue != null) {
339        if (!double.IsNaN(doubleValue.Value) && !double.IsInfinity(doubleValue.Value))
340          ret = doubleValue.Value;
341      } else if (intValue != null)
342        ret = intValue.Value;
343      else if (timeSpanValue != null)
344        ret = timeSpanValue.Value.TotalSeconds;
345      else
[12595]346        ret = GetCategoricalValue(key, value.ToString());
[12554]347      return ret.Value;
348    }
349
[12595]350    private double? GetCategoricalValue(string key, string value) {
[12388]351      if (!categoricalMapping.ContainsKey(key)) {
352        categoricalMapping[key] = new Dictionary<object, double>();
353        var orderedCategories =
354          GetAvailableItems().Where(x => x.Data.ContainsKey(key)).Select(x => x.Data[key].ToString())
355            .Distinct().OrderBy(x => x, new NaturalStringComparer());
356        int count = 1;
357        foreach (var category in orderedCategories) {
358          categoricalMapping[key].Add(category, count);
359          count++;
360        }
361      }
362      if (!this.categoricalMapping[key].ContainsKey(value)) return null;
363      return this.categoricalMapping[key][value];
364    }
365    private double GetValue(RecursiveDataItem item, AxisDimension axisDimension) {
366      double value = double.NaN;
367      switch (axisDimension) {
368        case AxisDimension.Index:
369          value = itemToIndexMapping[item];
370          break;
371        default:
372          throw new ArgumentException("No handling strategy for " + axisDimension.ToString() + " is defined.");
373      }
374      return value;
375    }
376    private double GetValue(RecursiveDataItem item, SizeDimension sizeDimension) {
377      double value = double.NaN;
378      switch (sizeDimension) {
379        case SizeDimension.Constant:
[12459]380          value = 5;
[12388]381          break;
382        default:
383          throw new ArgumentException("No handling strategy for " + sizeDimension.ToString() + " is defined.");
384      }
385      return value;
386    }
387    private void RebuildInverseIndex() {
388      if (Content != null) {
389        itemToIndexMapping.Clear();
390        int i = 0;
391        foreach (var item in GetAvailableItems()) {
392          // ToDo: do not add if key (which one?) is not present within item
393          itemToIndexMapping.Add(item, i);
394          i++;
395        }
396      }
397    }
[12459]398
399    private void UpdateAxisLabels() {
400      Axis xAxis = chart.ChartAreas[0].AxisX;
401      Axis yAxis = chart.ChartAreas[0].AxisY;
402      //mkommend: combobox.SelectedIndex could not be used as this changes during hovering over possible values
[12498]403      var xSAxisSelected = XAxisValue == null ? null : (string)xAxisComboBox.SelectedItem;
404      var ySAxisSelected = YAxisValue == null ? null : (string)yAxisComboBox.SelectedItem;
[12459]405      SetCustomAxisLabels(xAxis, xSAxisSelected);
406      SetCustomAxisLabels(yAxis, ySAxisSelected);
[12498]407      if (XAxisValue != null)
408        xAxis.Title = XAxisValue;
409      if (YAxisValue != null)
410        yAxis.Title = YAxisValue;
[12459]411    }
412    private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) {
413      this.UpdateAxisLabels();
414    }
415    private void SetCustomAxisLabels(Axis axis, string key) {
[12595]416      if (key == null) return;
417      var tokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
418      if (tokens.Length == 2)
419        key = tokens[1];
[12459]420      axis.CustomLabels.Clear();
[12595]421      if (categoricalMapping.ContainsKey(key)) {
[12459]422        foreach (var pair in categoricalMapping[key]) {
423          string labelText = pair.Key.ToString();
424          CustomLabel label = new CustomLabel();
425          label.ToolTip = labelText;
426          if (labelText.Length > 25)
427            labelText = labelText.Substring(0, 25) + " ... ";
428          label.Text = labelText;
429          label.GridTicks = GridTickTypes.TickMark;
430          label.FromPosition = pair.Value - 0.5;
431          label.ToPosition = pair.Value + 0.5;
432          axis.CustomLabels.Add(label);
433        }
[12595]434      } else if (Content.Data.ContainsKey(key) && Content.Data[key] is TimeSpanValue) { // TODO
[12459]435        chart.ChartAreas[0].RecalculateAxesScale();
436        for (double i = axis.Minimum; i <= axis.Maximum; i += axis.LabelStyle.Interval) {
437          TimeSpan time = TimeSpan.FromSeconds(i);
438          string x = string.Format("{0:00}:{1:00}:{2:00}", time.Hours, time.Minutes, time.Seconds);
439          axis.CustomLabels.Add(i - axis.LabelStyle.Interval / 2, i + axis.LabelStyle.Interval / 2, x);
440        }
441      }
442    }
[12598]443
444    private void UpdateMarkerSizes() {
445      var series = chart.Series[0];
446      if (series.Points.Count <= 0) return;
447
448      var sizeValues = series.Points.Select(p => p.YValues[1]);
449      double minSizeValue = sizeValues.Min();
450      double maxSizeValue = sizeValues.Max();
451      double sizeRange = maxSizeValue - minSizeValue;
452
[12647]453      const int smallestBubbleSize = 5;
[12598]454
455      foreach (DataPoint point in series.Points) {
456        //calculates the relative size of the data point  0 <= relativeSize <= 1
457        double relativeSize = (point.YValues[1] - minSizeValue);
458        if (sizeRange > double.Epsilon) {
459          relativeSize /= sizeRange;
460
461          //invert bubble sizes if the value of the trackbar is negative
[12600]462          if (sizeTrackBar.Value < 0) relativeSize = Math.Abs(relativeSize - 1);
[12598]463        } else relativeSize = 1;
464
[12600]465        double sizeChange = Math.Abs(sizeTrackBar.Value) * relativeSize;
[12598]466        point.MarkerSize = (int)Math.Round(sizeChange + smallestBubbleSize);
467      }
468    }
[12881]469
470    private void UpdateDataPointJitter() {
471      var xAxis = this.chart.ChartAreas[0].AxisX;
472      var yAxis = this.chart.ChartAreas[0].AxisY;
473
474      double xAxisRange = xAxis.Maximum - xAxis.Minimum;
475      double yAxisRange = yAxis.Maximum - yAxis.Minimum;
476
477      foreach (DataPoint point in chart.Series[0].Points) {
478        var tag = (Tuple<double, double, RecursiveDataItem, RecursiveDataItem>)point.Tag;
479        var xItem = tag.Item3;
480        var yItem = tag.Item4;
481        double xValue = tag.Item1;
482        double yValue = tag.Item2;
483
484        if (!xJitterFactor.IsAlmost(0.0))
485          xValue += 0.1 * GetXJitter(xItem) * xJitterFactor * (xAxisRange);
486        if (!yJitterFactor.IsAlmost(0.0))
487          yValue += 0.1 * GetYJitter(yItem) * yJitterFactor * (yAxisRange);
488
489        point.XValue = xValue;
490        point.YValues[0] = yValue;
491      }
492
493      if (xJitterFactor.IsAlmost(0.0) && yJitterFactor.IsAlmost(0.0)) {
494        SetAutomaticUpdateOfAxis(xAxis, true);
495        SetAutomaticUpdateOfAxis(yAxis, true);
496        chart.ChartAreas[0].RecalculateAxesScale();
497      } else {
498        SetAutomaticUpdateOfAxis(xAxis, false);
499        SetAutomaticUpdateOfAxis(yAxis, false);
500      }
501    }
502    private double GetXJitter(RecursiveDataItem item) {
503      if (!this.xJitter.ContainsKey(item))
504        this.xJitter[item] = random.NextDouble() * 2.0 - 1.0;
505      return this.xJitter[item];
506    }
507    private double GetYJitter(RecursiveDataItem item) {
508      if (!this.yJitter.ContainsKey(item))
509        this.yJitter[item] = random.NextDouble() * 2.0 - 1.0;
510      return this.yJitter[item];
511    }
512
513    // sets an axis to automatic or restrains it to its current values
514    // this is used that none of the set values is changed when jitter is applied, so that the chart stays the same
515    private void SetAutomaticUpdateOfAxis(Axis axis, bool enabled) {
516      if (enabled) {
517        axis.Maximum = double.NaN;
518        axis.Minimum = double.NaN;
519        axis.MajorGrid.Interval = double.NaN;
520        axis.MajorTickMark.Interval = double.NaN;
521        axis.LabelStyle.Interval = double.NaN;
522      } else {
523        axis.Minimum = axis.Minimum;
524        axis.Maximum = axis.Maximum;
525        axis.MajorGrid.Interval = axis.MajorGrid.Interval;
526        axis.MajorTickMark.Interval = axis.MajorTickMark.Interval;
527        axis.LabelStyle.Interval = axis.LabelStyle.Interval;
528      }
529    }
[12882]530    private void UpdateCursorInterval() {
531      double xMin = double.MaxValue;
532      double xMax = double.MinValue;
533      double yMin = double.MaxValue;
534      double yMax = double.MinValue;
535
536      foreach (var point in chart.Series[0].Points) {
537        if (point.IsEmpty) continue;
538        if (point.XValue < xMin) xMin = point.XValue;
539        if (point.XValue > xMax) xMax = point.XValue;
540        if (point.YValues[0] < yMin) yMin = point.YValues[0];
541        if (point.YValues[0] > yMax) yMax = point.YValues[0];
542      }
543
544      double xRange = 0.0;
545      double yRange = 0.0;
546      if (xMin != double.MaxValue && xMax != double.MinValue) xRange = xMax - xMin;
547      if (yMin != double.MaxValue && yMax != double.MinValue) yRange = yMax - yMin;
548
549      if (xRange.IsAlmost(0.0)) xRange = 1.0;
550      if (yRange.IsAlmost(0.0)) yRange = 1.0;
551      double xDigits = (int)Math.Log10(xRange) - 3;
552      double yDigits = (int)Math.Log10(yRange) - 3;
553      double xZoomInterval = Math.Pow(10, xDigits);
554      double yZoomInterval = Math.Pow(10, yDigits);
555      this.chart.ChartAreas[0].CursorX.Interval = xZoomInterval;
556      this.chart.ChartAreas[0].CursorY.Interval = yZoomInterval;
557
558      // TODO
559      //code to handle TimeSpanValues correct
560      //int axisDimensionCount = Enum.GetNames(typeof(AxisDimension)).Count();
561      //int columnIndex = xAxisComboBox.SelectedIndex - axisDimensionCount;
562      //if (columnIndex >= 0 && Content.GetValue(0, columnIndex) is TimeSpanValue)
563      //  this.chart.ChartAreas[0].CursorX.Interval = 1;
564      //columnIndex = yAxisComboBox.SelectedIndex - axisDimensionCount;
565      //if (columnIndex >= 0 && Content.GetValue(0, columnIndex) is TimeSpanValue)
566      //  this.chart.ChartAreas[0].CursorY.Interval = 1;
567    }
568
[12884]569    private void chart_MouseMove(object sender, MouseEventArgs e) {
570      if (Control.MouseButtons != MouseButtons.None) return;
571      HitTestResult h = this.chart.HitTest(e.X, e.Y);
572      string newTooltipText = string.Empty;
573      string oldTooltipText;
574      if (h.ChartElementType == ChartElementType.DataPoint) {
575        var tag = (Tuple<double, double, RecursiveDataItem, RecursiveDataItem>)((DataPoint)h.Object).Tag;
576        newTooltipText = BuildTooltip(tag);
577      } else if (h.ChartElementType == ChartElementType.AxisLabels) {
578        newTooltipText = ((CustomLabel)h.Object).ToolTip;
579      }
580
581      oldTooltipText = this.tooltip.GetToolTip(chart);
582      if (newTooltipText != oldTooltipText)
583        this.tooltip.SetToolTip(chart, newTooltipText);
584    }
585
586    private string BuildTooltip(Tuple<double, double, RecursiveDataItem, RecursiveDataItem> tag) {
587      string tooltip;
588      if (tag.Item3 != tag.Item4)
589        tooltip = "X: " + tag.Item3.Name + Environment.NewLine + "Y: " + tag.Item4.Name + Environment.NewLine;
590      else
591        tooltip = tag.Item3.Name + Environment.NewLine;
592
593      double? xValue = tag.Item1;
594      double? yValue = tag.Item2;
595      //double? sizeValue = this.GetValue(run, (string)sizeComboBox.SelectedItem);
596
597      string xString = xValue == null ? string.Empty : xValue.Value.ToString();
598      string yString = yValue == null ? string.Empty : yValue.Value.ToString();
599      //string sizeString = sizeValue == null ? string.Empty : sizeValue.Value.ToString();
600
601      // TODO
602      ////code to handle TimeSpanValues correct
603      //int axisDimensionCount = Enum.GetNames(typeof(AxisDimension)).Count();
604      //int columnIndex = xAxisComboBox.SelectedIndex - axisDimensionCount;
605      //if (xValue.HasValue && columnIndex > 0 && Content.GetValue(0, columnIndex) is TimeSpanValue) {
606      //  TimeSpan time = TimeSpan.FromSeconds(xValue.Value);
607      //  xString = string.Format("{0:00}:{1:00}:{2:00.00}", (int)time.TotalHours, time.Minutes, time.Seconds);
608      //}
609      //columnIndex = yAxisComboBox.SelectedIndex - axisDimensionCount;
610      //if (yValue.HasValue && columnIndex > 0 && Content.GetValue(0, columnIndex) is TimeSpanValue) {
611      //  TimeSpan time = TimeSpan.FromSeconds(yValue.Value);
612      //  yString = string.Format("{0:00}:{1:00}:{2:00.00}", (int)time.TotalHours, time.Minutes, time.Seconds);
613      //}
614
615      tooltip += xAxisComboBox.SelectedItem + " : " + xString + Environment.NewLine;
616      tooltip += yAxisComboBox.SelectedItem + " : " + yString + Environment.NewLine;
617      //tooltip += sizeComboBox.SelectedItem + " : " + sizeString + Environment.NewLine;
618
619      return tooltip;
620    }
621
622    private void chart_MouseDoubleClick(object sender, MouseEventArgs e) {
623      HitTestResult h = this.chart.HitTest(e.X, e.Y, ChartElementType.DataPoint);
624      if (h.ChartElementType == ChartElementType.DataPoint) {
625        var tuple = (Tuple<double, double, RecursiveDataItem, RecursiveDataItem>)((DataPoint)h.Object).Tag;
626        IContentView view = MainFormManager.MainForm.ShowContent(tuple.Item3, typeof(RecursiveDataItemView));
627        if (view != null) {
628          view.ReadOnly = this.ReadOnly;
629          view.Locked = this.Locked;
630        }
631        if (tuple.Item3 != tuple.Item4) {
632          view = MainFormManager.MainForm.ShowContent(tuple.Item4, typeof(RecursiveDataItemView));
633          if (view != null) {
634            view.ReadOnly = this.ReadOnly;
635            view.Locked = this.Locked;
636          }
637        }
638
639        this.chart.ChartAreas[0].CursorX.SelectionStart = this.chart.ChartAreas[0].CursorX.SelectionEnd;
640        this.chart.ChartAreas[0].CursorY.SelectionStart = this.chart.ChartAreas[0].CursorY.SelectionEnd;
641      }
642      UpdateAxisLabels();
643    }
[12388]644    #endregion
645
646    #region Event Handlers
647    private void treeView_AfterCheck(object sender, TreeViewEventArgs e) {
[12499]648      if (updating) return;
649      updating = true;
[12388]650      UpdateComboBoxes();
[12498]651      UpdateDataPoints();
[12499]652      updating = false;
[12388]653    }
[12459]654    private void treeView_AfterSelect(object sender, TreeViewEventArgs e) {
655      //UpdateNodeColors();
656      //UpdateComboBoxes();
657      //UpdateDataPoints();
658    }
[12388]659
660    private void axisComboBox_SelectedValueChanged(object sender, EventArgs e) {
[12499]661      if (updating) return;
662      updating = true;
[12388]663      UpdateDataPoints();
[12459]664      UpdateAxisLabels();
[12499]665      updating = false;
[12388]666    }
[12459]667
668    private void includeChildrenCheckBox_CheckedChanged(object sender, EventArgs e) {
[12499]669      if (updating) return;
670      updating = true;
[12459]671      UpdateTreeViewCheckBoxes();
672      UpdateComboBoxes();
[12499]673      updating = false;
[12459]674    }
675
676    private void levelNumericUpDown_ValueChanged(object sender, EventArgs e) {
[12499]677      if (updating) return;
678      updating = true;
[12459]679      UpdateTreeViewCheckBoxes();
680      UpdateComboBoxes();
[12499]681      updating = false;
[12459]682    }
[12600]683
684    private void sizeTrackBar_ValueChanged(object sender, EventArgs e) {
685      UpdateMarkerSizes();
686    }
[12881]687
688    private void jitterTrackBar_ValueChanged(object sender, EventArgs e) {
689      this.xJitterFactor = xJitterTrackBar.Value / 100.0;
690      this.yJitterFactor = yJitterTrackBar.Value / 100.0;
691      UpdateDataPointJitter();
692    }
[12388]693    #endregion
[12459]694
695    #region Helpers
696    private IEnumerable<TreeNode> IterateAllNodes() {
697      return Collect(treeView.Nodes);
698    }
699    private IEnumerable<TreeNode> IterateCheckedNodes() {
700      return IterateAllNodes().Where(t => t.Checked);
701    }
702    private IEnumerable<TreeNode> Collect(TreeNodeCollection nodes) {
703      foreach (TreeNode node in nodes) {
704        yield return node;
705        foreach (var child in Collect(node.Nodes))
706          yield return child;
707      }
708    }
709    #endregion
[12388]710  }
711}
Note: See TracBrowser for help on using the repository browser.