Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 12657 was 12647, checked in by pfleck, 9 years ago

#2379
Added support for multidimensional values.
Adapted sample.

File size: 21.9 KB
Line 
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)")]
35  [Content(typeof(RecursiveDataItem), false)]
36  public partial class BubbleChartView : ItemView {
37    private const string Separator = ":";
38
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
45    private string XAxisValue { get { return (string)xAxisComboBox.SelectedItem; } }
46    private string YAxisValue { get { return (string)yAxisComboBox.SelectedItem; } }
47    private string SizeAxisValue { get { return (string)sizeComboBox.SelectedItem; } }
48
49    private bool updating = false;
50
51    public new RecursiveDataItem Content {
52      get { return (RecursiveDataItem)base.Content; }
53      set { base.Content = value; }
54    }
55
56    public BubbleChartView() {
57      InitializeComponent();
58
59      chart.CustomizeAllChartAreas();
60      chart.ChartAreas[0].CursorX.Interval = 1;
61      chart.ChartAreas[0].CursorY.Interval = 1;
62      chart.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
63      chart.ChartAreas[0].AxisY.ScaleView.Zoomable = true;
64    }
65
66    protected override void OnContentChanged() {
67      base.OnContentChanged();
68
69      UpdateTreeView();
70      UpdateLevelControl();
71
72      UpdateComboBoxes();
73      UpdateDataPoints();
74      //UpdateCaption();
75    }
76    protected override void SetEnabledStateOfControls() {
77      base.SetEnabledStateOfControls();
78      levelNumericUpDown.Enabled = Content != null;
79      // ToDo
80    }
81
82    #region Tree Queries
83    private IEnumerable<RecursiveDataItem> GetAvailableItems() {
84      return IterateCheckedNodes()
85        .Select(n => (RecursiveDataItem)n.Tag);
86    }
87    private IEnumerable<string> GetAvailableKeys() {
88      var collector = new HashSet<string>();
89      GetAvailableKeys(Content, collector);
90      return collector;
91    }
92    private void GetAvailableKeys(RecursiveDataItem node, ISet<string> collector) {
93      foreach (var child in node.Children) {
94        foreach (var key in child.Data.Keys) {
95          collector.Add(node.Name + Separator + key);
96        }
97      }
98
99      foreach (var child in node.Children)
100        GetAvailableKeys(child, collector);
101    }
102    #endregion
103
104    #region Update Controls
105    private void UpdateLevelControl() {
106      if (Content == null) return;
107      if (treeView.Nodes.Count > 0)
108        levelNumericUpDown.Maximum = IterateAllNodes().Max(t => t.Level);
109      else
110        levelNumericUpDown.Maximum = 0;
111    }
112
113    private void UpdateTreeView() {
114      treeView.Nodes.Clear();
115      if (Content != null)
116        treeView.Nodes.Add(CreateTreeNode(Content));
117      treeView.ExpandAll();
118      splitContainer.Panel1Collapsed = treeView.Nodes.Count == 0;
119      if (treeView.Nodes.Count > 0)
120        treeView.SelectedNode = treeView.Nodes[0];
121    }
122    private TreeNode CreateTreeNode(RecursiveDataItem item) {
123      var node = new TreeNode(item.Name) {
124        Tag = item,
125        Checked = true
126      };
127      foreach (var child in item.Children)
128        node.Nodes.Add(CreateTreeNode(child));
129      return node;
130    }
131
132    private void UpdateTreeViewCheckBoxes() {
133      int level = (int)levelNumericUpDown.Value;
134      bool includeChildren = includeChildrenCheckBox.Checked;
135      foreach (var node in IterateAllNodes()) {
136        bool @checked = includeChildren ? node.Level >= level : node.Level == level;
137        if (node.Checked != @checked)
138          node.Checked = @checked;
139      }
140    }
141
142    private void UpdateComboBoxes() {
143      var selectedXAxis = (string)xAxisComboBox.SelectedItem;
144      var selectedYAxis = (string)yAxisComboBox.SelectedItem;
145      var selectedSizeAxis = (string)sizeComboBox.SelectedItem;
146      xAxisComboBox.Items.Clear();
147      yAxisComboBox.Items.Clear();
148      sizeComboBox.Items.Clear();
149
150      if (Content != null) {
151        var axisNames = GetAvailableKeys().ToArray();
152        var additionalAxisDimension = Enum.GetNames(typeof(AxisDimension));
153        var additionalSizeDimension = Enum.GetNames(typeof(SizeDimension));
154
155        xAxisComboBox.Items.AddRange(additionalAxisDimension);
156        xAxisComboBox.Items.AddRange(axisNames);
157        yAxisComboBox.Items.AddRange(additionalAxisDimension);
158        yAxisComboBox.Items.AddRange(axisNames);
159        sizeComboBox.Items.AddRange(additionalSizeDimension);
160        sizeComboBox.Items.AddRange(axisNames);
161
162        bool changed = false;
163        if (selectedXAxis != null && xAxisComboBox.Items.Contains(selectedXAxis)) {
164          xAxisComboBox.SelectedItem = selectedXAxis;
165          changed = true;
166        }
167        if (selectedYAxis != null && yAxisComboBox.Items.Contains(selectedYAxis)) {
168          yAxisComboBox.SelectedItem = selectedYAxis;
169          changed = true;
170        }
171        if (selectedSizeAxis != null && sizeComboBox.Items.Contains(selectedSizeAxis)) {
172          sizeComboBox.SelectedItem = selectedSizeAxis;
173          changed = true;
174        } else sizeComboBox.SelectedItem = SizeDimension.Constant.ToString();
175        if (changed) {
176          UpdateDataPoints();
177          UpdateAxisLabels();
178        }
179      }
180    }
181
182    private void UpdateDataPoints() {
183      var series = chart.Series[0];
184      series.Points.Clear();
185      itemToIndexMapping.Clear();
186      categoricalMapping.Clear();
187      RebuildInverseIndex();
188
189      chart.ChartAreas[0].AxisX.IsMarginVisible = !(XAxisValue != null && XAxisValue.Contains(AxisDimension.Index.ToString()));
190      chart.ChartAreas[0].AxisY.IsMarginVisible = !(YAxisValue != null && YAxisValue.Contains(AxisDimension.Index.ToString()));
191
192      if (Content != null) {
193        var items = GetAvailableItems().ToList();
194        var xss = GetValues(items, XAxisValue).Select(x => x.ToList()).ToList();
195        var yss = GetValues(items, YAxisValue).Select(x => x.ToList()).ToList();
196        var sss = GetValues(items, SizeAxisValue).Select(x => x.ToList()).ToList();
197
198        if (xss.Any(xs => xs.Any()) && yss.Any(ys => ys.Any()) && sss.Any(ss => ss.Any())) {
199          var xDim = Tuple.Create(xss.Count, xss.Max(xs => xs.Count));
200          var yDim = Tuple.Create(yss.Count, yss.Max(ys => ys.Count));
201          var sDim = Tuple.Create(sss.Count, sss.Max(ss => ss.Count));
202
203          var isSimple = new Func<Tuple<int, int>, bool>(dim => dim.Item1 == 1 && dim.Item2 > 1 || dim.Item1 > 1 && dim.Item2 == 1);
204          var flatten = new Func<List<List<double>>, List<double>>(ss => ss.SelectMany(s => s).ToList());
205
206          if (isSimple(xDim) && isSimple(yDim)) {
207            var xs = flatten(xss);
208            var ys = flatten(yss);
209            if (xs.Count == ys.Count) {
210              var xsEnum = xs.GetEnumerator();
211              var ysEnum = ys.GetEnumerator();
212              while (xsEnum.MoveNext() & ysEnum.MoveNext())
213                series.Points.Add(new DataPoint(xsEnum.Current, new[] { ysEnum.Current, sss[0][0] }));
214            } /*else {
215              foreach (var x in xs)
216                foreach (var y in ys)
217                  series.Points.Add(new DataPoint(x, new[] { y, sss[0][0] }));
218            }*/
219          } else if (isSimple(xDim)) {
220            var xs = flatten(xss);
221            if (xs.Count == yss.Count) {
222              var xsEnum = xs.GetEnumerator();
223              var yssEnum = yss.GetEnumerator();
224              while (xsEnum.MoveNext() & yssEnum.MoveNext()) {
225                var x = xsEnum.Current;
226                var ys = yssEnum.Current;
227                foreach (var y in ys)
228                  series.Points.Add(new DataPoint(x, new[] { y, sss[0][0] }));
229              }
230            }
231          } else if (isSimple(yDim)) {
232            var ys = flatten(yss);
233            if (xss.Count == ys.Count) {
234              var xssEnum = xss.GetEnumerator();
235              var ysEnum = ys.GetEnumerator();
236              while (xssEnum.MoveNext() & ysEnum.MoveNext()) {
237                var xs = xssEnum.Current;
238                var y = ysEnum.Current;
239                foreach (var x in xs)
240                  series.Points.Add(new DataPoint(x, new[] { y, sss[0][0] }));
241              }
242            }
243          } else {
244            if (xss.Count == yss.Count) {
245              var xssEnum = xss.GetEnumerator();
246              var yssEnum = yss.GetEnumerator();
247              while (xssEnum.MoveNext() & yssEnum.MoveNext()) {
248                var xs = xssEnum.Current;
249                var ys = yssEnum.Current;
250                if (xs.Count == ys.Count) {
251                  var xsEnum = xs.GetEnumerator();
252                  var ysEnum = ys.GetEnumerator();
253                  while (xsEnum.MoveNext() & ysEnum.MoveNext())
254                    series.Points.Add(new DataPoint(xsEnum.Current, new[] { ysEnum.Current, sss[0][0] }));
255                }
256              }
257            } else if (xDim.Item2 == yDim.Item2) {
258              foreach (var xs in xss) {
259                foreach (var ys in yss) {
260                  var xsEnum = xs.GetEnumerator();
261                  var ysEnum = ys.GetEnumerator();
262                  while (xsEnum.MoveNext() & ysEnum.MoveNext()) {
263                    series.Points.Add(new DataPoint(xsEnum.Current, new[] { ysEnum.Current, sss[0][0] }));
264                  }
265                }
266              }
267            }
268          }
269        }
270        UpdateMarkerSizes();
271      }
272    }
273
274    /*var items = GetAvailableItems();
275          foreach (var item in items) {
276            var xs = GetValues(item, XAxisValue).ToList();
277            var ys = GetValues(item, YAxisValue).ToList();
278            var ss = GetValues(item, SizeAxisValue).ToList();
279            if (xs.Count > 0 && ys.Count > 0 && (ss.Count == 1 || ss.Count == xs.Count || ss.Count == ys.Count)) {
280              var sEnum = ss.Count > 1 ? ss.GetEnumerator() : Enumerable.Repeat(ss[0], xs.Count * ys.Count).GetEnumerator();
281              sEnum.MoveNext();
282              if (xs.Count > 1 && ys.Count > 1) { // index matching
283                for (int i = 0; i < Math.Min(xs.Count, ys.Count); i++)
284                  series.Points.Add(new DataPoint(xs[i], new[] { ys[i], sEnum.Current }));
285              } else {
286                foreach (var x in xs) {
287                  foreach (var y in ys) {
288                    series.Points.Add(new DataPoint(x, new[] { y, sEnum.Current }));
289                    sEnum.MoveNext();
290                  }
291                }
292              }
293            }
294          }*/
295
296    private IEnumerable<IEnumerable<double>> GetValues(IEnumerable<RecursiveDataItem> items, string key) {
297      if (key == null) yield break;
298      var keyTokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
299      if (keyTokens.Length == 1) {
300        if (Enum.IsDefined(typeof(SizeDimension), key)) {
301          var sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), key);
302          yield return GetValue(null, sizeDimension).ToEnumerable();
303        } else
304          foreach (var item in items) {
305            if (Enum.IsDefined(typeof(AxisDimension), key)) {
306              var axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), key);
307              yield return GetValue(item, axisDimension).ToEnumerable();
308            } else if (item.Data.ContainsKey(key)) {
309              yield return item.Data[key] as IEnumerable<double> ?? ConvertToDouble(item, key).ToEnumerable();
310            }
311          }
312      } else if (keyTokens.Length == 2) {
313        string parentName = keyTokens[0];
314        key = keyTokens[1];
315        foreach (var item in items.Where(item => item.Name == parentName)) {
316          foreach (var child in item.Children.Where(child => child.Data.ContainsKey(key))) {
317            yield return child.Data[key] as IEnumerable<double> ?? ConvertToDouble(child, key).ToEnumerable();
318          }
319        }
320      } else throw new InvalidOperationException("key either contains a single key or a child/data key");
321
322    }
323
324    private IEnumerable<double> GetValues(RecursiveDataItem parent, string key) {
325      if (parent == null || string.IsNullOrEmpty(key))
326        return Enumerable.Empty<double>();
327      var tokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
328      if (tokens.Length == 2) {
329        var parentKey = tokens[0];
330        var dataKey = tokens[1];
331        if (parent.Name == parentKey) {
332          return parent.Children
333            .Where(child => child.Data.ContainsKey(dataKey))
334            .Select(child => ConvertToDouble(child, dataKey));
335        }
336        return Enumerable.Empty<double>();
337      } else {
338        var item = parent;
339        if (item == null || string.IsNullOrEmpty(key))
340          return Enumerable.Empty<double>();
341
342        if (Enum.IsDefined(typeof(AxisDimension), key)) {
343          var axisDimension = (AxisDimension)Enum.Parse(typeof(AxisDimension), key);
344          return GetValue(item, axisDimension).ToEnumerable();
345        } else if (Enum.IsDefined(typeof(SizeDimension), key)) {
346          var sizeDimension = (SizeDimension)Enum.Parse(typeof(SizeDimension), key);
347          return GetValue(item, sizeDimension).ToEnumerable();
348        } else if (item.Data.ContainsKey(key)) {
349
350          return ConvertToDouble(item, key).ToEnumerable();
351        } else {
352          return Enumerable.Empty<double>();
353        }
354      }
355    }
356
357    private double ConvertToDouble(RecursiveDataItem item, string key) {
358      IItem value = item.Data[key];
359      var doubleValue = value as DoubleValue;
360      var intValue = value as IntValue;
361      var timeSpanValue = value as TimeSpanValue;
362      double? ret = null;
363      if (doubleValue != null) {
364        if (!double.IsNaN(doubleValue.Value) && !double.IsInfinity(doubleValue.Value))
365          ret = doubleValue.Value;
366      } else if (intValue != null)
367        ret = intValue.Value;
368      else if (timeSpanValue != null)
369        ret = timeSpanValue.Value.TotalSeconds;
370      else
371        ret = GetCategoricalValue(key, value.ToString());
372      return ret.Value;
373    }
374
375    private double? GetCategoricalValue(string key, string value) {
376      if (!categoricalMapping.ContainsKey(key)) {
377        categoricalMapping[key] = new Dictionary<object, double>();
378        var orderedCategories =
379          GetAvailableItems().Where(x => x.Data.ContainsKey(key)).Select(x => x.Data[key].ToString())
380            .Distinct().OrderBy(x => x, new NaturalStringComparer());
381        int count = 1;
382        foreach (var category in orderedCategories) {
383          categoricalMapping[key].Add(category, count);
384          count++;
385        }
386      }
387      if (!this.categoricalMapping[key].ContainsKey(value)) return null;
388      return this.categoricalMapping[key][value];
389    }
390    private double GetValue(RecursiveDataItem item, AxisDimension axisDimension) {
391      double value = double.NaN;
392      switch (axisDimension) {
393        case AxisDimension.Index:
394          value = itemToIndexMapping[item];
395          break;
396        default:
397          throw new ArgumentException("No handling strategy for " + axisDimension.ToString() + " is defined.");
398      }
399      return value;
400    }
401    private double GetValue(RecursiveDataItem item, SizeDimension sizeDimension) {
402      double value = double.NaN;
403      switch (sizeDimension) {
404        case SizeDimension.Constant:
405          value = 5;
406          break;
407        default:
408          throw new ArgumentException("No handling strategy for " + sizeDimension.ToString() + " is defined.");
409      }
410      return value;
411    }
412    private void RebuildInverseIndex() {
413      if (Content != null) {
414        itemToIndexMapping.Clear();
415        int i = 0;
416        foreach (var item in GetAvailableItems()) {
417          // ToDo: do not add if key (which one?) is not present within item
418          itemToIndexMapping.Add(item, i);
419          i++;
420        }
421      }
422    }
423
424    private void UpdateAxisLabels() {
425      Axis xAxis = chart.ChartAreas[0].AxisX;
426      Axis yAxis = chart.ChartAreas[0].AxisY;
427      //mkommend: combobox.SelectedIndex could not be used as this changes during hovering over possible values
428      var xSAxisSelected = XAxisValue == null ? null : (string)xAxisComboBox.SelectedItem;
429      var ySAxisSelected = YAxisValue == null ? null : (string)yAxisComboBox.SelectedItem;
430      SetCustomAxisLabels(xAxis, xSAxisSelected);
431      SetCustomAxisLabels(yAxis, ySAxisSelected);
432      if (XAxisValue != null)
433        xAxis.Title = XAxisValue;
434      if (YAxisValue != null)
435        yAxis.Title = YAxisValue;
436    }
437    private void chart_AxisViewChanged(object sender, System.Windows.Forms.DataVisualization.Charting.ViewEventArgs e) {
438      this.UpdateAxisLabels();
439    }
440    private void SetCustomAxisLabels(Axis axis, string key) {
441      if (key == null) return;
442      var tokens = key.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
443      if (tokens.Length == 2)
444        key = tokens[1];
445      axis.CustomLabels.Clear();
446      if (categoricalMapping.ContainsKey(key)) {
447        foreach (var pair in categoricalMapping[key]) {
448          string labelText = pair.Key.ToString();
449          CustomLabel label = new CustomLabel();
450          label.ToolTip = labelText;
451          if (labelText.Length > 25)
452            labelText = labelText.Substring(0, 25) + " ... ";
453          label.Text = labelText;
454          label.GridTicks = GridTickTypes.TickMark;
455          label.FromPosition = pair.Value - 0.5;
456          label.ToPosition = pair.Value + 0.5;
457          axis.CustomLabels.Add(label);
458        }
459      } else if (Content.Data.ContainsKey(key) && Content.Data[key] is TimeSpanValue) { // TODO
460        chart.ChartAreas[0].RecalculateAxesScale();
461        for (double i = axis.Minimum; i <= axis.Maximum; i += axis.LabelStyle.Interval) {
462          TimeSpan time = TimeSpan.FromSeconds(i);
463          string x = string.Format("{0:00}:{1:00}:{2:00}", time.Hours, time.Minutes, time.Seconds);
464          axis.CustomLabels.Add(i - axis.LabelStyle.Interval / 2, i + axis.LabelStyle.Interval / 2, x);
465        }
466      }
467    }
468
469    private void UpdateMarkerSizes() {
470      var series = chart.Series[0];
471      if (series.Points.Count <= 0) return;
472
473      var sizeValues = series.Points.Select(p => p.YValues[1]);
474      double minSizeValue = sizeValues.Min();
475      double maxSizeValue = sizeValues.Max();
476      double sizeRange = maxSizeValue - minSizeValue;
477
478      const int smallestBubbleSize = 5;
479
480      foreach (DataPoint point in series.Points) {
481        //calculates the relative size of the data point  0 <= relativeSize <= 1
482        double relativeSize = (point.YValues[1] - minSizeValue);
483        if (sizeRange > double.Epsilon) {
484          relativeSize /= sizeRange;
485
486          //invert bubble sizes if the value of the trackbar is negative
487          if (sizeTrackBar.Value < 0) relativeSize = Math.Abs(relativeSize - 1);
488        } else relativeSize = 1;
489
490        double sizeChange = Math.Abs(sizeTrackBar.Value) * relativeSize;
491        point.MarkerSize = (int)Math.Round(sizeChange + smallestBubbleSize);
492      }
493    }
494    #endregion
495
496    #region Event Handlers
497    private void treeView_AfterCheck(object sender, TreeViewEventArgs e) {
498      if (updating) return;
499      updating = true;
500      UpdateComboBoxes();
501      UpdateDataPoints();
502      updating = false;
503    }
504    private void treeView_AfterSelect(object sender, TreeViewEventArgs e) {
505      //UpdateNodeColors();
506      //UpdateComboBoxes();
507      //UpdateDataPoints();
508    }
509
510    private void axisComboBox_SelectedValueChanged(object sender, EventArgs e) {
511      if (updating) return;
512      updating = true;
513      UpdateDataPoints();
514      UpdateAxisLabels();
515      updating = false;
516    }
517
518    private void includeChildrenCheckBox_CheckedChanged(object sender, EventArgs e) {
519      if (updating) return;
520      updating = true;
521      UpdateTreeViewCheckBoxes();
522      UpdateComboBoxes();
523      updating = false;
524    }
525
526    private void levelNumericUpDown_ValueChanged(object sender, EventArgs e) {
527      if (updating) return;
528      updating = true;
529      UpdateTreeViewCheckBoxes();
530      UpdateComboBoxes();
531      updating = false;
532    }
533
534    private void sizeTrackBar_ValueChanged(object sender, EventArgs e) {
535      UpdateMarkerSizes();
536    }
537    #endregion
538
539    #region Helpers
540    private IEnumerable<TreeNode> IterateAllNodes() {
541      return Collect(treeView.Nodes);
542    }
543    private IEnumerable<TreeNode> IterateCheckedNodes() {
544      return IterateAllNodes().Where(t => t.Checked);
545    }
546    private IEnumerable<TreeNode> Collect(TreeNodeCollection nodes) {
547      foreach (TreeNode node in nodes) {
548        yield return node;
549        foreach (var child in Collect(node.Nodes))
550          yield return child;
551      }
552    }
553    #endregion
554  }
555}
Note: See TracBrowser for help on using the repository browser.